- Input & output
- Functions
- Variables
- Null safety
- Classes and instances
- Conditional expressions
- Loops
- Collections
- DTO
- Default values for function parameters
- Lazy property
- Extension functions
- Try-catch expression
- Single-expression functions
- Call multiple methods on an object instance (with)
- Configure properties of an object (apply)
- Mark code as incomplete (TODO)
println("Hello from Kotlin. Enter your name:")
val name = readln()
print("Hello ${name}")
fun sum(a: Int, b: Int): Int {
return a + b
}
fun sum(a: Int, b: Int) = a + b
Read-only local variables are defined using the keyword val. They can be assigned a value only once.
val a: Int = 1 // immediate assignment
val b = 2 // `Int` type is inferred
val c: Int // Type required when no initializer is provided
val unsignedInt: UInt = 1234u // unsigned types
Variables that can be reassigned use the var keyword.
var x = 5 // `Int` type is inferred
x += 1
var a: String = "abc" // Regular initialization means non-nullable
val l = a.length // it's guaranteed not to cause an NPE
var b: String? = "abc" // can be set to null
println(b?.length) // safe call This returns b.length if b is not null, and null otherwise
bob?.department?.head?.name // Such a chain returns null if any of the properties in it is null
To perform a certain operation only for non-null values, you can use the safe call operator together with let
val listWithNulls: List<String?> = listOf("Kotlin", null)
for (item in listWithNulls) {
item?.let { println(it) } // prints Kotlin and ignores null
}
// If either `person` or `person.department` is null, the function is not called:
person?.department?.head = managersPool.getManager()
Elvis operator
val l: Int = if (b != null) b.length else -1
val l = b?.length ?: -1
val name = node.getName() ?: throw IllegalArgumentException("name expected")
The !! operator. Converts any value to a non-nullable type and throws an exception if the value is null
val l = b!!.length
Safe casts Regular casts may result in a ClassCastException if the object is not of the target type. Another option is to use safe casts that return null if the attempt was not successful:
val aInt: Int? = a as? Int
Collections of a nullable type
val nullableList: List<Int?> = listOf(1, 2, null, 4)
val intList: List<Int> = nullableList.filterNotNull()
Create class
class Rectangle(val height: Double, val length: Double) {
val perimeter = (height + length) * 2
}
Create instance
val rectangle = Rectangle(5.0, 2.0)
println("The perimeter is ${rectangle.perimeter}")
Classes are final by default; to make a class inheritable, mark it as open
.
open class Shape
Inheritance between classes is declared by a colon (:
).
class Rectangle(val height: Double, val length: Double): Shape() {
val perimeter = (height + length) * 2
}
if
fun maxOf(a: Int, b: Int): Int {
if (a > b) {
return a
} else {
return b
}
}
short if
fun maxOf(a: Int, b: Int) = if (a > b) a else b
if with result
val y = if (x == 1) {
"one"
} else if (x == 2) {
"two"
} else {
"other"
}
for loop
val items = listOf("apple", "banana", "kiwifruit")
for (item in items) {
println(item)
}
for (index in items.indices) {
println("item at $index is ${items[index]}")
}
while loop
val items = listOf("apple", "banana", "kiwifruit")
var index = 0
while (index < items.size) {
println("item at $index is ${items[index]}")
index++
}
when expression
fun describe(obj: Any): String =
when (obj) {
1 -> "One"
"Hello" -> "Greeting"
is Long -> "Long"
!is String -> "Not a string"
else -> "Unknown"
}
ranges
val x = 10
val y = 9
if (x in 1..y+1) {
println("fits in range")
}
iterate over range
for (x in 1..5) {
print(x)
}
Or over a progression.
for (x in 1..10 step 2) {
print(x)
}
println()
for (x in 9 downTo 0 step 3) {
print(x)
}
Iterate over a collection.
for (item in items) {
println(item)
}
val numbers = listOf("one", "two", "three", "four")
numbers.forEach {
println(it)
}
Check if a collection contains an object using in operator:
when {
"orange" in items -> println("juicy")
"apple" in items -> println("apple is fine too")
}
Use lambda expressions to filter and map collections:
val fruits = listOf("banana", "avocado", "apple", "kiwifruit")
fruits
.filter { it.startsWith("a") }
.sortedBy { it }
.map { it.uppercase() }
.forEach { println(it) }
If you provide a comma-separated list of collection elements as arguments, the compiler detects the element type automatically. When creating empty collections, specify the type explicitly.
val numbersSet = setOf("one", "two", "three", "four")
val emptySet = mutableSetOf<String>()
map
Note that the to notation creates a short-living Pair object, so it's recommended that you use it only if performance isn't critical.
To avoid excessive memory usage, use alternative ways. For example, you can create a mutable map and populate it using the write operations.
The apply()
function can help to keep the initialization fluent here.
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)
val numbersMap = mutableMapOf<String, String>().apply { this["one"] = "1"; this["two"] = "2" }
Another way of creating a collection is to call a builder function – buildList()
, buildSet()
, or buildMap()
.
They create a new, mutable collection of the corresponding type
val map = buildMap { // this is MutableMap<String, Int>, types of key and value are inferred from the `put()` calls below
put("a", 1)
put("b", 0)
put("c", 4)
}
println(map) // {a=1, b=0, c=4}
Empty collections
val empty = emptyList<String>()
For lists, there is a constructor-like function that takes the list size and the initializer function that defines the element value based on its index.
val doubled = List(3, { it * 2 }) // or MutableList if you want to change its content later
println(doubled) // [0, 2, 4]
Concrete type constructors
val linkedList = LinkedList<String>(listOf("one", "two", "three"))
Collection copying functions, such as toList()
, toMutableList()
, toSet()
create a snapshot of a collection at a specific moment. Their result is a new collection of the same elements.
If you add or remove elements from the original collection, this won't affect the copies.
val sourceList = mutableListOf(1, 2, 3)
val copySet = sourceList.toMutableSet()
copySet.add(3)
copySet.add(4)
println(copySet) // [1, 2, 3, 4]
Invoke functions on other collections
val numbers = listOf("one", "two", "three", "four")
val longerThan3 = numbers.filter { it.length > 3 }
val numbers = setOf(1, 2, 3)
println(numbers.map { it * 3 })
println(numbers.mapIndexed { idx, value -> value * idx })
val numbers = listOf("one", "two", "three", "four")
println(numbers.associateWith { it.length }) // {one=3, two=3, three=5, four=4}
Create DTOs (POJOs/POCOs)
data class Customer(val name: String, val email: String)
data class User(val name: String = "", val age: Int = 0)
To exclude a property from the generated implementations, declare it inside the class body
(.equals()
only evaluates properties from the primary constructor):
data class Person(val name: String) {
var age: Int = 0
}
provides a Customer class with the following functionality: getters (and setters in case of vars) for all properties
equals()
hashCode()
toString()
copy()
fun foo(a: Int = 0, b: String = "") { ... }
val p: String by lazy { // the value is computed only on first access
// compute the string
}
fun String.spaceToCamelCase() { ... }
"Convert this to camelcase".spaceToCamelCase()
fun test() {
val result = try {
count()
} catch (e: ArithmeticException) {
throw IllegalStateException(e)
}
// Working with result
}
fun theAnswer() = 42
the same as
fun theAnswer(): Int {
return 42
}
class Turtle {
fun penDown()
fun penUp()
fun turn(degrees: Double)
fun forward(pixels: Double)
}
val myTurtle = Turtle()
with(myTurtle) { //draw a 100 pix square
penDown()
for (i in 1..4) {
forward(100.0)
turn(90.0)
}
penUp()
}
This is useful for configuring properties that aren't present in the object constructor.
val myRectangle = Rectangle().apply {
length = 4
breadth = 5
color = 0xFAFAFA
}
Kotlin's standard library has a TODO() function that will always throw a NotImplementedError. Its return type is Nothing so it can be used regardless of expected type. There's also an overload that accepts a reason parameter:
fun calcTaxes(): BigDecimal = TODO("Waiting for feedback from accounting")