Kotlin Quick Reference for Java Developer

Abstract: Kotlin quick reference for Java developer.

Packages

Unlike Java, Kotlin doesn’t impose any restrictions on the layout and naming of source files: you can put multiple classes in the same file, and choose any directory structure you’re comfortable with.

However, it’s still a good practice to follow Java’s directory layout, while not hesitating to group multiple classes into the same file if they’re small and related.

Variables

In Kotlin, there are two keywords to declare a variable:

  • val — Immutable reference. A val variable can’t be reassigned after initialization and corresponds to a final variable in Java.
  • var — Mutable reference. The value of such variable can be changed.
1
2
val i = 42  // final variable  
var j = 42 // mutable variable

Semicolons in Kotlin are optional.

Kotlin’s variable declaration starts with one of the two keywords above, followed by the variable name. The variable type may come after the name, separated by a colon. Just like Java, Kotlin is a statically typed language, but the compiler is able to determine the type from the context, this is called type inference:

1
2
3
4
5
val hello: String = "Hello"  // explicit type  
val hello = "Hello" // inferred typeval i = 42 // Int
val l = 42L // Long
val d = 42.0 // Double
val f = 42f // Float

Kotlin supports top-level variables, you don’t need to declare them in a class.

Equality

Unlike Java, Kotlin’s == operator compares two objects by calling equals under the hood. For reference comparison, you can use the === operator instead:

1
2
3
val a = Pair("key", 42)  
val b = Pair("key", 42)println(a == b) // true
println(a === b) // false

For values which are represented as primitive types at runtime, the === check is equivalent to the == check.

Null Safety

The first and most important difference between Kotlin and Java is Kotlin’s explicit support for nullable types. Putting a question mark after the type name explicitly allows the variable to contains null:

1
2
3
4
5
6
7
8
9
10
var nickname: String? = null
var name: String = "John"

name = null
^ ERROR: null can not be a value of a non-null type String.

nickname = name

name = nickname
^ ERROR: Type mismatch.

A type without a question mark denotes that variables of such type can’t store null references.

Thankfully, Kotlin provides useful tools to deal with nullable types, and the safe call operator will soon become your best friend:

1
2
// returns null if nickname is null  
val uppercase = nickname**?.**toUpperCase()

The safe call operator above pairs extremely well with the Elvis operator:

1
2
3
// return "unknown" if nickname is null  
val safe = nickname **?:** "unknown"// return 0 if nickname is null
val length = nickname?.length **?:** 0

If you’re missing NPE, you can still throw an exception if the value is null using the not-null assertion operator:

// throw an NPE if nickname is null
val length = nickname!!.length

String Templates

Kotlin allows you to refer to local variables in string literals:

1
2
val name = "Kotlin"  
val hello = "Hello $name" // "Hello Kotlin"

The literal expression is statically checked and you’re not restricted to variable names, you can also use more complex expressions:

1
println("Hello ${if (array.size > 0) array[0] else "foo"}!")

Primitive Types

Unlike Java, Kotlin doesn’t distinguish between primitive and wrapper types. You’ll find below the full list of types that correspond to Java primitives:

  • Integer types — Byte, Short, Int, Long
  • Floating-point types — Float, Double
  • Character type — Char
  • Boolean type — Boolean

Since there’s no straightforward equivalent to Java primitives, you may be concerned about efficiency, right? Don’t worry, the Kotlin compiler will ensure to use the most efficient type where applicable: Kotlin’s Double type will be compiled to the Java primitive type double, unless being used in a Collection for example or allowed to be null.

Another important difference between Kotlin and Java, is numeric conversions. Kotlin doesn’t automatically convert numbers from one type to another, the conversion must be explicit:

1
2
3
val i = 42  
val l: Long = i
**^ Error: Type Mismatch**

Unsigned integers support is coming to Kotlin.

Arrays

Unlike Java, arrays are just classes in Kotlin. Array instances can be created using the arrayOf, arrayOfNulls and emptyArray standard library functions:

1
2
3
val integers: Array<Int> = arrayOf(1, 2, 3)  
val strings: Array<String?> = arrayOfNulls(10)
val empty: Array<Double> = emptyArray()

Beware that type arguments of Array always become object types: an Array<Int> will compile to java.lang.Integer[] ! To represent an array of primitive types, Kotlin provide separate array classes, one for each primitive type: IntArray, DoubleArray, BooleanArray and so on:

1
2
3
val threeZeros = IntArray(3)  
val threeZerosToo = intArrayOf(0, 0, 0)
val threeZerosAgain = arrayOf(0, 0, 0).toIntArray()

Enum

Kotlin’s enums are declared by adding the enum keyword in front of the class header:

Root Type

Similar to Java’s Object, the Any type is the supertype of all non-nullable types in Kotlin. Unlike Java, Any is also the supertype of primitive types such as Int:

1
2
val i: Any = 42     // automatic boxing  
val j: Any? = null // nullable

Under the hood, the Any type corresponds to java.lang.Object

Type Casts

The is operator checks if an expression is an instance of a type:

1
2
3
4
5
6
7
fun sizeOf(obj: Any): Int? {  
if(obj is String) return obj.length // no cast needed!
if(obj is Array<*>) return obj.size
if(obj is Collection<*>) return obj.size
return null
}if(obj !is String)
print("$obj is not a String)

The as operator tries to cast a value to a type:

1
2
3
4
fun foo(obj: Any) {  
val i = obj as Int // may throw ClassCastException!
...
}

The safe cast operator as? tries to cast a value to the specified type and returns null if the value doesn’t have the proper type. The safe cast operator pairs extremely well with the Elvis operator:

1
val stringOrEmpty = obj as? String ?: ""

Iterating

Kotlin supports both while and do-while loops and doesn’t bring anything new to it.

In Kotlin the for loop construct iterates through anything that provides an iterator but, unlike Java, doesn’t support any other form than the well-known for..in syntax:

1
2
val array = arrayOf("Kotlin", "Java", "Gradle")  
for(s in array) println(s)

To iterate over numbers, instead, Kotlin supports the concept of range_._ _A_s the name implies, a range is just an interval between two values, usually numbers but not limited to:

1
2
3
4
5
6
7
8
9
10
11
// inferred type is IntRange  
val oneToTen = 1..10// inferred type is CharRange
val alphabet = 'a'..'z'// inclusive
for(c **in** alphabet)
println("$c")// inclusive
for(i **in** 0..100)
println("$i of 100")// exclusive
for(i **in** 0 **until** 100)
println("$i of 99")// downward with step
for (i **in** 100 **downTo** 0 **step** 2)
println("$i left down to 0")

_until_, _downTo_ and _step_ are not Kotlin keywords but infix extension functions from the standard library (more on this later).

The .. operator, or range operator, when applied to integral types returns an IntRange object which is a subclass of Iterable<Int> over which the for statement iterates. In fact, the 0..100 expression expands to 0.rangeTo(100) under the hood.

Working with arrays or collections, using the destructuring syntax along with the withIndex standard library function or the indices property, you can keep track of the index:

1
2
3
for((index, element) in array.withIndex())   
println("array[$index] = $element")for(index in array.indices)
println("array[$index] = ${array[index]}")

Iterating over a map is also easier thanks to the destructuring syntax:

1
2
for((key, value) in my_map)  
println("map[$key] = $value")

You can use the in operator to check whether a value is in a range, or the opposite:

1
2
3
4
5
if(c in 'a'..'z') {  
println("$c is a lowercase letter")
} else if(c !in '0'..'9') {
println("$c is not a digit")
}

Functions

Kotlin’s functions declaration starts with the fun keyword, followed by the function name and the parameter list in parentheses. The return type comes after the parameter list, separated by a colon:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// block body
fun sum(a: Int, b: Int) : Int {
return a + b
}

// expression body, inferred return type
fun sum(a: Int, b: Int) = a + b

// variable of a function type
val foo: (Int, Int) -> Int = sum

// variable of a nullable function type
var foo: ((Int, Int) -> Int)? = sum

// Unit is Kotlin's void (it is optional)

fun hello(name: String) : Unit {
println("Hello $name!")
}

Kotlin supports top level functions; you can declare functions at the top level of a file, you don’t need to put them in a class.

Unlike Java, Kotlin supports default parameters and named arguments:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun join(strings: Collection<String>, separator: String = " ", 
prefix: String = "", postfix: String = "") : String
{
val result = StringBuilder(prefix)
for((index, element) in strings.withIndex()) {
if(index > 0) result.append(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}

join(list)
join(list, separator = ",")
join(list, prefix = "(", postfix = ")")

Kotlin’s functions can accept an arbitrary number of argument using the vararg keyword, which pairs extremely well with the spread operator:

1
2
3
4
5
6
7
8
9
10
fun hello(vararg values: String) {
for(value in values) println("Hello $value!")
}

hello("Kotlin", "Java", "Groovy")

// calls using the spread operator
val names = arrayOf("Java", "Groovy")
hello(*names)
hello("Kotlin", *names)

Extension Functions

An extension function is a function that can be called as a member of a class, but is defined outside of it:

1
2
3
4
5
6
7
package strings

fun String.last(): Char = get(length - 1)
fun String.isYelling(): Boolean = last() == '!'

// extension for nullable type
fun String?.isNull(): Boolean = this == null

Extension functions do not have access to private or protected members of the receiver class and doesn’t automatically become available across your entire project, it needs to be imported just like any other class or function:

1
2
3
4
5
6
import strings.last  
import strings.isYelling
// or
import strings.*val c = "Kotlin".last()
val b = "Java!".isYelling()val s: String? = null
val ok = s.isNull() // no safe call needed!

You must be aware that extension functions behavior is slightly different from member functions:

  • Member functions always take precedence if they share the same signature.
  • Extension functions resolve to the declared static type of the variable, not to the runtime type of the value:
1
2
3
4
5
6
7
8
fun Any.yell() = println("Any!")  
fun String.yell() = println("String!")

val str : String = "Hello"
val obj : Any = str

str.yell() // String!
obj.yell() // Any!

Infix Functions

Functions marked with the [infix](https://kotlinlang.org/docs/reference/functions.html#infix-notation) keyword can also be called by omitting the dot and the parentheses:

1
2
3
4
5
6
7
8
9
class Price(val value: Double, val currency: Currency)

infix fun Int.euro(cents: Int): Price {
return Price(toDouble() + cents / 100.0, Currency.EURO)
}

val price = 1 euro 42
// equivalent to:
val price = 1.euro(42)

Kotlin’s standard library already provides a few infix functions, not to be confused with keywords:

1
2
3
4
5
6
7
8
9
10
// until and step are infix functions
for(i in 0 until 100 step 2) println("$i")
// equivalent to:
for(i in 0.until(100).step(2)) println("$i")

// downTo is an infix function
for(i in 100 downTo 0) println("$i")

// shl, and, xor are infix functions
val i = (0x65acf9 shl 6) and 0x55 xor 0x80

infix functions must be member functions or extension functions, and declare a single parameter with no default value.

Lambdas

In Kotlin, a lambda is always surrounded with curly braces and the arrow separates the argument list from the body. When passed as the last or only argument to a function, more concise constructs are possible:

1
2
3
4
5
6
7
8
9
10
11
// Classic  
people.filter({ p: Person -> p.age < 18 })
// Lambda as last argument can be moved out of parentheses!
people.filter() { p: Person -> p.age < 18}

// Lambda as the only argument? You can remove parentheses!
people.filter { p: Person -> p.age < 18 }
// Inferred parameter type
people.filter { p -> p.age < 18 }
// Using the default parameter name
people.filter { it.age < 18 }

Unlike Java, you can access non-final variables and even modify them:

1
2
var empty = 0
strings.forEach { element -> if(element.isEmpty()) empty++ }

Similar to how extension functions works, you can also declare the receiver of a lambda by prefixing its declaration with the type of the receiver:

1
2
3
4
5
6
7
8
9
10
11
fun buildString(action: StringBuilder.() -> Unit) : String {
val sb = StringBuilder()
sb.action()
return sb.toString()
}

// inside the lambda, `this` refer to the StringBuilder
val s = buildString {
append("Hello!")
append("How are you?")
}

The buildString helper above could be implemented using the Kotlin’s standard library [with](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/with.html) function, which also makes use of lambdas with receiver:

1
2
3
4
5
val s = with(StringBuilder()) {
append("Hello!")
append("How are you?")
toString() // implicit return value
}

Inline Functions

Functions declared with the inline modifier are directly substituted into places where the function is called instead of being invoked:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
inline fun greeter(action: () -> Unit) {
try {
println("Hello!")
action()
}finally{
println("Goodbye!")
}
}

greeter {
println("How are you?")
}

// directly expands to:

try {
println("Hello!")
println("How are you?")
}finally{
println("Goodbye!")
}

As a side effect, a bare return statement in a lambda called from an inline function will return from the enclosing function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Kotlin's standard library extension
inline fun Array<String>.forEach(action: (String) -> Unit) {
for(str in this) {
action(str)
}
}

val list = listOf("Kotlin", "Java", "Groovy")

// a bare return statement in a lambda called from
// an inline function return from the enclosing function.
fun main() {
list.forEach {
println(it)
if(it == "Kotlin") return // return from main !
}
println("never reached")
}

// because it expands to:

fun main() {
for(str in list) {
println(str)
if(str == "Kotlin") return
}
println("never reached")
}

Instead, you can use returns with labels:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
val list = listOf("Kotlin", "", "Java", "Groovy")

fun main() {
list.forEach {
if(it.isNullOrEmpty()) return@forEach // return with implicit label
println(it)
}
println("Done!")
}

// equivalent to:

fun main() {
list.forEach there@{ str ->
if(it.isNullOrEmpty()) return@there // return with explicit label
println(str)
}
println("Done!")
}

When

The [when](https://kotlinlang.org/docs/reference/control-flow.html#when-expression) construct may look a lot like the switch statement, but it is more powerful:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// when is an expression, not a statement!
fun symbolOf(currency: Currency) : String {
return when(currency) {
Currency.USD -> "$"
Currency.EUR -> "€"
Currency.GBP -> "£"
Currency.BTC -> "₿"
Currency.ETH -> "⧫"
else -> {
log("warning: unknown symbol")
"?"
}
}
}

// multiple cases
fun Currency.isCrypto() = when(this) {
Currency.BTC, Currency.ETH -> true
else -> false
}

fun Currency.isFiat() = !isCrypto()

// when without an argument
fun canTransfer(source: Currency, destination: Currency) = when {
source.isCrypto() && destination.isCrypto() -> true
source.isFiat() && destination.isFiat() -> true
else -> false
}

Arbitrary expressions can be used as branch conditions:

1
2
3
4
5
6
7
8
9
when(x) {
42 -> print("the answer")
0xbadbabe, 0xbadface -> print("l33t s534k!")
is String -> print("$[x.toUpperCase()]")
in 0..10 -> print("0 ≤ x ≤ 10")
!in 6..9 -> print("x < 6 and x > 9")
parseInt("0xcafe") -> print("no sugar please")
else -> print("?")
}

If you have complex statement for branch, can just use like that:

1
2
3
4
val v1:String = "good"
when {
v1.contains("good") -> println(v1)
}

Exceptions

Use the throw exception to throw an exception object, but remember there’s no new keyword in Kotlin:

1
throw Exception("Oops!")

The try..catch block is now an expression:

1
2
3
4
5
6
7
8
9
10
11
12
13
// the returned value is the last expression of the try or catch block
val a: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }

fun printNumber(reader: BufferedReader) {

val number = try {
parseInt(reader.readLine())
} catch (e: NumberFormatException) {
return
}

println(number)
}

In Kotlin, all exceptions are unchecked, meaning that the compiler doesn’t force you to catch any of them:

1
2
// Java would requires us to catch IOException here  
bufferedReader.close()

Classes

Like Java, classes in Kotlin are declared using the class keyword, but curly braces are optional if there’s no body. Unlike Java, Kotlin doesn’t have a new keyword:

1
2
3
class Empty  
class Person { ... }
val p = Person()

Constructors

A class in Kotlin can have a single primary constructor, and one (or more) secondary constructors. The primary constructor goes after the class name:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Primary constructor declaration
class Person constructor(name: String) { ... }

// The constructor keyword can be omitted if there's
// no visibility modifiers nor any annotations
class Person(name: String) { ... }

// Property initialization
class Person(_firstName: String, _lastName: String) {
val firstName = _firstName.capitalize()
val lastName = _lastName.capitalize()
val fullName: String

init { // using initializer blocks
fullName = "$firstName $lastName"
}
}

// You can declare properties in the primary constructor
class Person(val firstName: String, val lastName: String, var age: Int)

// Kotlin supports default arguments
class Person(val firstName: String = "John", val lastName: String = "Doe")

Secondary constructors are declared in the class body using the constructor keyword:

1
2
3
4
5
6
7
8
9
10
class Person(val name: String) {
// secondary constructors must delegate to the primary constructor
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}

init {
println("I'm called before the secondary constructor")
}
}

Inheritance

Kotlin’s common superclass is Any, that’s also the default for a class with no supertypes declared. To declare an explicit supertype, place the type after a colon in the class header:

1
2
3
4
// the base class primary constructor must be called (if any)  
class Derived(p: Int) : Base(p)
// multiple supertypes are separated by a comma
class Derived: Base(), Listener

Properties

As you may have noticed above, properties in Kotlin can be declared as mutable using the var keyword, or immutable using val, and can also be declared using the primary constructor:

1
2
3
4
5
6
class Point {  
var x: Double
var y: Double
}// equivalent to

class Point(var x: Double, var y: Double)

Properties in Kotlin can have getters and setters, also known as custom accessors:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// custom getter on an immutable property (no setter allowed)
class Person(val name: String, val birthday: LocalDate) {
val age: Long
get() = birthday.until(LocalDate.now(), ChronoUnit.YEARS)
}

// custom setter
class Contact {
var firstName: String? = null
set(value) {
field = value?.capitalize()
}
}

// private setter on a public property
class CharacterCounter {
var count: Int = 0
private set

fun add(str: String) = count += str.length
}

Using the by keyword, properties can be delegated to an instance through the setValue and getValue operator:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Capitalizer {
var value: String? = null

operator fun getValue(thisRef: Any?, property: KProperty<*>): String? {
return value
}

operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String?) {
this.value = value?.capitalize()
}
}

class Person {
var firstName by Capitalizer()
var lastName by Capitalizer()
}

The Kotlin standard library provides the lazy function that takes a lambda and returns an instance of Lazy<T> and can serve as a delegate:

1
2
3
4
5
6
class DeepThought {
val answer: String by lazy {
sleep(7_500_000, ChronoUnit.YEARS)
"42"
}
}

As a convenience, Kotlin also provides the lateinit modifier to leave non-null properties un-initialized:

1
2
3
4
5
6
7
8
9
class Presenter {
lateinit var view: View

fun setup(_view: View) {
// ensure the lateinit var hasn't been initialized
if(this::view.isInitialized) throw IllegalStateException()
view = _view
}
}

Access Modifiers

Unlike Java, Kotlin’s default modifiers are public and final, and you’ll have to explicitly mark classes and members with the open modifier if required. Kotlin also introduced the internal modifier, which restricts accesses to the current module only, while the private modifier is now allowed for top-level declarations:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// final public class (default)
class A

// final private class (only visible in the file)
private class B

// final internal class (only visible in the module)
internal class C

// abstract public class
abstract class D

// final public class, private constructor
class E private constructor(val a: Int)

// sub-classable public class
open class F {

private val a = 1 // private
protected open val b = 2 // overridable protected
internal val c = 3 // non-overridable, only visible in the module
val d = 4 // non-overridable public (default)

// final public method (default)
fun xyz() { ... }

// overridable public method
open fun foo() { ... }

// final protected method
protected fun baz() { ... }

// overridable protected method
open protected fun bar() { ... }

// private method
private fun qux() { ... }
}

// final public subclass
class F1: F() {
// a is not visible
final override val b = 42 // locked override (protected)
// c and d are visible

final override foo() { ... } // final override
override bar() { ... } // override (protected)
}

// sub-classable public subclass
open class F2: F() {
override public b = 43 // override, public promoted
override public bar() { ... } // override, public promoted
}

// access-modifiers are accepted into the primary constructor
open class G(private val a: Int, protected open val b: Int,
internal val c: Int, val d: Int)

Interfaces

Interfaces in Kotlin can contain abstract properties along with method declarations and implementations:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface A {
val a: Int // abstract
val b: Int // abstract
fun foo() // abstract
fun bar() {
// optionnal body
}
}

interface B { ... }

class C: A, B {
override val a: Int = 42
override val b: Int
get() = a + 1

override foo() { ... }
override bar() { ... }
}

Thanks to Kotlin’s built-in support for delegation, a derived class implementing a given interface can delegate all (or some) of its public members to a specified object using the by keyword:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface View {
fun click()
fun toggle()
}

class ViewImpl: View {
override fun click() { println("click!") }
override fun toggle() { println("toggle!") }
}

class EmptyDelegate(view: View): View by view

class OverridingDelegate(view: View): View by view {
override fun click() { println("CLICK!") }
}

fun main() {
val v = ViewImpl()
EmptyDelegate(v).click()
OverridingDelegate(v).click()
}

Extension Properties

Similar to extension functions, extension properties provide a way to extend classes using the property syntax instead of the function syntax:

1
2
3
4
5
6
7
8
val String.last: Char  
get() = get(length - 1)

var StringBuilder.last: Char
get() = get(length - 1)
set(value: Char) {
setCharAt(length - 1, value)
}

Operator Overloading

Kotlin uses convention methods instead of relying on types to define implementations for a predefined set of operators. To implement an operator, you can provide a member function or an extension function. Functions that overload operators need to be marked as operator:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Point(val x: Double, val y: Double) {
// addition operator
operator fun plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}

// overload using Double
operator fun plus(value: Double): Point {
return Point(x + value, y + value)
}
}

// using extension function
operator fun Char.times(multiplier: Int): String {
return toString().repeat(multiplier)
}

print('!' * 5) // "!!!!!"

You’ll find below the list of the predefined operators, excluding delegated properties operators for brevity:

╔═══════════════════╦═════════════════════════════════╗
║ Expression ║ Translate to ║
╠═══════════════════╬═════════════════════════════════╣
║ a + b ║ a.plus(b) ║
║ a - b ║ a.minus(b) ║
║ a b ║ a.times(b) ║
║ a / b ║ a.div(b) ║
║ a % b ║ a.rem(b) ║
║ +a ║ a.unaryPlus() ║
║ -a ║ a.unaryMinus() ║
║ !a ║ a.not() ║
║ a++ ║ a.inc() ║
║ a– ║ a.dec() ║
║ a..b ║ a.rangeTo(b) ║
║ a in b ║ b.contains(a) ║
║ a !in b ║ !b.contains(a) ║
║ a[i] ║ a.get(i) ║
║ a[i, j] ║ a.get(i, j) ║
║ a[i, …, z] ║ a.get(i, …, z) ║
║ a[i] = b ║ a.set(i, b) ║
║ a[i, j] = b ║ a.set(i, j, b) ║
║ a[i, …, z] = b ║ a.set(i, …, z, b) ║
║ a() ║ a.invoke() ║
║ a(i) ║ a.invoke(i) ║
║ a(i, j) ║ a.invoke(i, j) ║
║ a(i, …, z) ║ a.invoke(i, …, z) ║
║ a+=b ║ a.plusAssign(b) ║
║ a-=b ║ a.minusAssign(b) ║
║ a
=b ║ a.timesAssign(b) ║
║ a/=b ║ a.divAssign(b) ║
║ a%=b ║ a.remAssign(b) ║
║ a == b ║ a?.equals(b) ?: (b === null) ║
║ a != b ║ !(a?.equals(b) ?: (b === null)) ║
║ a > b ║ a.compareTo(b) > 0 ║
║ a < b ║ a.compareTo(b) < 0 ║
║ a >= b ║ a.compareTo(b) >= 0 ║
║ a <= b ║ a.compareTo(b) <= 0 ║
║ for(a in b) ║ b.iterator() ║
║ val (x, y, z) = a ║ val x = a.component1() ║
║ ║ val y = a.component2() ║
║ ║ val z = a.component3() ║
╚═══════════════════╩═════════════════════════════════╝

Both inc and dec operators must return a value and shouldn’t mutate the object.

Data Classes

Kotlin provides autogenerated equals, hashCode, toString and copy methods implementations for free by adding the data modifier to your class:

1
2
3
4
5
6
data class Person(val name: String, val age: Int)
val john = Person("John", 21)
println(john) // "Person(name=John, age=21)"
val bob = john.copy(name="Bob")
println(bob) // "Person(name=Bob, age=21)"
println(bob == bob.copy()) // true

Nested Classes

Unlike Java, inner classes in Kotlin don’t have access to the outer class instance, unless explicitly requested by adding the inner modifier. Kotlin’s default behavior is equivalent to a static nested class in Java, but an outer class doesn’t see private members of its inner (or nested) classes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A {
// nested class (equivalent to static in Java)
class B {
// A can't see private members of nested classes
private val invisible = 42
}

// inner class
inner class C {
// A can't see private members of inner classes
private fun invisible() = 42
}
}

val b = A.B()
val c = A().C()

Sealed Classes

Sealed classes are used to represent restricted class hierarchy: a sealed class can have subclasses, but all of them must be declared in the same file as the sealed class itself:

1
2
3
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()

A sealed class is abstract by itself, it cannot be instantiated directly and can have abstract members.

Classes which extend subclasses of a sealed class (indirect inheritors) can be placed anywhere, not necessarily in the same file.

The object keyword

The object keyword defines a class and creates an instance of that class at the same time. The singleton pattern comes immediately in mind as a use case:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// object class as a singleton
object Log {
val prop = 42
fun debug(msg: String) { ... }
fun info(msg: String) { ... }
fun warn(msg: String) { ... }
fun error(msg: String) { ... }
}

// object classes can inherit from classes and interfaces
object DirectExecutor: Executor {
override fun execute(r: Runnable) = r.run()
}

DirectExecutor.execute( runnable {
Log.debug("executing...")
})

Anonymous objects replace Java’s anonymous inner classes but, unlike Java, they can implement multiple interfaces:

1
2
3
4
5
6
7
val listener = object: OnMenuClickListener, OnMenuExpandListener {
override fun onMenuClick(item: Menu) { ... }
override fun onMenuExpand(item: Menu) { ... }
}

menu.setOnClickListener(listener)
menu.setOnExpandListener(listener)

Classes in Kotlin can’t have static members since there’s no such keyword in the Kotlin language. Instead, top-level and object declarations are recommended but if you need to access private members of an outer class, companion objects are the way to go:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person private constructor(val name: String, val age: Int) {

companion object Factory {
fun create(name: String, age: Int) = Person(name, age)
}
}

// members of the companion object can be called using the class name as qualifier
val bob = Person.create("Bob", 21)

// the name of the class acts as a reference to the companion object
val factory = Person
// equivalent to:
val factory = Person.Factory

Conclusion

There are still quite a few unexplored corners like coroutines, generics or java interoperability but, if you come that far, you should be ready to switch to Kotlin.

Do not forget to dive into the Koans exercises to practice what you learned, while keeping a hand at the official documentation. You can also write and execute Kotlin’s snippets online!

That guide will be updated over time and suggestions, so do not hesitate to follow me? Thanks for reading!