![Learn Kotlin](../../img/training/learn-kotlin.png)
In few words Kotlin is
Concise Drastically reduce the amount of boilerplate code
Interoperable Leverage existing libraries for the JVM, Android, and the browser. You can call Kotlin code in Java or Java code in Kotlin
Safe Kotlin tries to help you reduce errors like null pointer exceptions
For several years Java has been trying to catch up with Koltin. Kotlin has allowed a questioning of Java but Java will still take a long time to catch up.
An executable Java class is a class which, when handed over to the JVM, starts its execution at a particular point in the class, the main method.
For example
public class HelloWorldApplication {
public static void main(String[] args) {
String name = "Guillaume";
System.out.println("Hello EMSE I am " + name);
}
}
In IntelliJ you can use the contextual menu (right click) to run this class and see the result in console
Hello EMSE I am Guillaume
With Kotlin you can write to produce the same result.
fun main(args: Array<String>) {
val name = "Guillaume"
println("Hello EMSE I am $name")
}
You can write functions not attached to a class (the compiler will do it for you)
The public
visibility is the default in Kotlin and therefore no need to define it each time
Semicolons are no longer necessary
Kotlin does a lot of type inference (the compiler tries to guess which type you are using) and you don’t need to define the type if the compiler can infer it (example of the name or you don’t need to specify the type String)
You can use String templates and directly access the content of a variable with $
If you want to test Kotlin code in your browser you can use https://play.kotlinlang.org
Kotlin use basic types. The most used are
Integer numbers : Int (Integer in Java), Long
Floating-point number : Double, Float
String
Boolean
Arrays
Collections : List, Set, Map…
Kotlin forces you to use immutability when you develop. An immutable object is an object whose state cannot be modified after it is created. It allows you to write safer and cleaner code.
When you want to declare a variable you can use the keyword val. We did that in our first example
val name = "Guillaume"
When the value is defined you can’t update it. With the code below, the compiler will fail with an Error "Val cannot be reassigned".
name = "Someone else"
If you need to reassign the value you can use keyword var
var name = "Guillaume"
name = "Someone else"
Collections (List, Set, Map…) are also immutable in Kotlin. The code below will fail because type List is immutable and method add does not exist
val rooms: List@LTRoom@GT = listOf()
rooms.add(Room(1, "Room1"))
When you want a mutable collection you have dedicated types
val rooms: MutableList@LTRoom@GT = mutableListOf()
rooms.add(Room(1, "Room1"))
One of the most common pitfalls in many programming languages, including Java, is that accessing a member of a null reference will result in a null reference exception. Kotlin’s type system is aimed at eliminating the danger of null references from code.
var a: String = "abc" // Regular initialization means non-null by default
a = null // compilation error
In Kotlin, the type system distinguishes between references that can hold null (nullable references) and those that can not (non-null references). To allow nulls, we can declare a variable as nullable string, written String?:
var b: String? = "abc" // can be set null
b = null // ok
When you want declare a nullable value add ? to the type
For more details read this article
A function is define with the keyword fun. In Kotlin. Arguments args, returned type are always after For example
fun double(x: Int): Int {
return 2 * x
}
You can call this function
val result = double(2)
You can use default argument in Kotlin. For example:
fun double(x: Int = 4): Int {
return 2 * x
}
double(2) // returns 4
double() // returns 8 (the default value is applied)
When calling a function, you can name one or more of its arguments. This may be helpful when a function has a large number of arguments
fun foo(bar: Int = 0, baz: Int) : Int { /*...*/ }
val result = foo(baz = 4)
Classes in Kotlin are declared using the keyword class. A class in Kotlin can have a primary constructor and one or more secondary constructors. The primary constructor is part of the class header: it goes after the class name (and optional type parameters).
class Person constructor(firstName: String) { /*...*/ }
If the primary constructor does not have any annotations or visibility modifiers, the constructor keyword can be omitted:
class Person(firstName: String) { /*...*/ }
By default, Kotlin classes are final: they can’t be inherited. To make a class inheritable, mark it with the open keyword.
open class Base(p: Int)
class Derived(p: Int) : Base(p)
For more detail read this article.
We frequently create classes whose main purpose is to hold data. In such a class some standard functionality and utility functions are often mechanically derivable from the data.
Example in Java
public class WindowDto {
private Long id;
private String name;
private WindowStatus windowStatus;
private String roomName;
private Long roomId;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public WindowStatus getWindowStatus() {
return windowStatus;
}
public void setWindowStatus(WindowStatus windowStatus) {
this.windowStatus = windowStatus;
}
public String getRoomName() {
return roomName;
}
public void setRoomName(String roomName) {
this.roomName = roomName;
}
public Long getRoomId() {
return roomId;
}
public void setRoomId(Long roomId) {
this.roomId = roomId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
WindowDto windowDto = (WindowDto) o;
return Objects.equals(name, windowDto.name) &&
Objects.equals(roomId, windowDto.roomId);
}
@Override
public int hashCode() {
return Objects.hash(id, name, windowStatus, roomName, roomId);
}
}
In Kotlin, you can use a data class to do the same thing
data class WindowDto(
val id: Long,
val name: String,
val windowStatus: WindowStatus,
val roomName: String,
val roomId: Long
)
The compiler automatically derives the following members from all properties declared in the primary constructor
equals()/hashCode() functions
toString() of the form "WindowDto(id=12, name=Window1, roomName=S12, roomId=23)";
copy() to easily copy this data class
The most basic usage of enum classes is implementing type-safe enums:
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
Interfaces in Kotlin can contain declarations of abstract methods, as well as method implementations. What makes them different from abstract classes is that interfaces cannot store state (they can have properties but these need to be abstract or to provide accessor implementations.)
An interface is defined using the keyword interface
interface MyInterface {
fun bar()
fun foo() {
// optional body
}
}
A class or object can implement one or more interfaces
class Child : MyInterface {
override fun bar() {
// body
}
}
When you program in Java or Kotlin, you very often use inner classes.
class HelloWorld {
public String name(){
return "Dev-Mind";
}
class A {
public void hello(){
System.out.println("Hello world" + name()); // Compilation error => method name() is not visible
}
}
}
Inner classes in Java are non-static by default, so you can use the global methods or attributes of the enclosing class in the inner class. For example in our example, class A
can use the name()
method.
A non-static inner class has a reference to its enclosing class. When ths inner class is no longer in use, the garbage collector cannot do its job and delete it. Indeed the inner class is considered active (used by the internal class). It is not a problem if your app use singletons (Spring). But in the Android world, on a device with limited resources, it’s more problematic. Especially if we use inner classes in objects which are very often destroyed and rebuilt (activities are deleted and recreated after each configuration change). Many developers get tricked into introducing memory leaks in their applications in this way.
In Java to avoid the problem you have to use static inner class
. In Kotlin when you create a nested class you do not have access to the variables and methods of the class (equivalent of a static inner class)
class HelloWorld {
fun name() = "Dev-Mind"
class A {
fun hello() {
println("Hello world" + name())
}
}
}
You can still create the equivalent of an inner class using the internal inner class
syntax. Once again, the language has chosen to simplify the most common use case.
To develop these exercices, you can use IntelliJ, Android or this website.
Create a main function to display the message "Hello Kotlin World" in the console
Create a data class to manage your rooms. You should define
a non nullable id
of type Long
a non nullabe name
of type String
a nullabe currentTemperature
of type Double
with a default value to null
Create an immutable List in your main function with several rooms. If your class is correct the following code will compile
val rooms = listOf(
RoomDto(1, "Room1"),
RoomDto(2, "Room2", 20.3),
RoomDto(id = 3, name = "Room3", currentTemperature = 20.3),
RoomDto(4, "Room4", currentTemperature = 19.3),
)
Display the name of each room in the console. You should use
a map function to extract the name,
a joinToString function to join all the value in a String with a ',' separator
a println
function to obtain Room1, Room2, Room3, Room4
in the console
Filter the rooms with a temperature greater than 20° and display the result in the console. You should obtain Room4
Declare a nullable variable called mainRoom in your code. Initialize this value with RoomDto(5, "Room5", currentTemperature = 19.3)
. Display in the console currentTemperature of the room (To compile your code you should use a ?
)
Create a function to compute the number of characters in a room name. This function must have one nullable room as argument.
When we program we use many external libraries, and we do not have control on them. Consider a use case. We have to do statistics by citizen age.
data class Citizen(val firstname: String,
val lastname: String,
val sexe: Sexe,
val birthdate: LocalDate)
To determine the age you can write a function
fun getAge(date: LocalDate) = LocalDate.now().year - date.year
val barackObama = Citizen("Barack", "Obama", Sexe.MALE, LocalDate.parse("1961-08-04"))
val barackAge = getAge(barackObama.birthdate)
With Kotlin you can also extend the LocalDate
class and create a new method (function extension) that will be specific to you and that you can use in your whole project. for example
fun LocalDate.getAge() = LocalDate.now().year - this.year
// With this function extension you can write
val barackAge = barackObama.birthdate.getAge()
Better instead of exposing a function you can expose a property
val LocalDate.age
get() = LocalDate.now().year - this.year
val barackAge = barackObama.birthdate.age
A higher order function is a function that takes a function as an argument. In this case you don’t need to pass a lambda when calling the method but you can add an execution block just after the method call
Said like that you must be lost and it’s normal
Kotlin used higher order functions (and extensions) to simplify the use of Java streams
public inline fun <T> Iterable<T>.find(predicate: (T) -> Boolean): T? {
return firstOrNull(predicate)
}
If we have a collection of speakers we can select the first one with the first name Guillaume via this code
val guillaume = speakers.firstOrNull {
it.firstname == "Guillaume" // it is the current item in the collection
}
// You can also write
val guillaume = speakers.firstOrNull { speaker ->
speaker.firstname == "Guillaume"
}
To remember in Java equivalent is
Speaker speaker = speakers.stream()
.filter(s -> s.getName().equals("Guillaume"))
.findFirst()
.orElse(null)
The Stream Java API is great to use, but the Kotlin collections and extension functions are even nicer.
Kotlin is increasingly known for the flexibility it offers to write a DSL with strong typing.
An example:
class Cell(val content: String)
class Row(val cells: MutableList@LTCell@GT = mutableListOf()) {
// Define an Higher-Order Function
fun cell(adder: () -> Cell): Row {
cells.add(adder())
return this
}
}
class Table(val rows: MutableList@LTRow@GT = mutableListOf()) {
// Define an Higher-Order Function
fun row(adder: () -> Row): Table {
rows.add(adder())
return this
}
}
In my Table
class I added a` row` function (with a function as argument) which allows to add a row. The same was done in the Row
class for a cell. So I can write
val table = Table()
.row { Row().cell { Cell("Test") }}
.row { Row().cell { Cell("Test2") }}
This is just an introduction. If you want to become a rock star in Kotlin you can read the official documentation: https://kotlinlang.org/docs/reference/