string interpolation
val foo = 1
var bar = "one is $foo"
var baz = "two is ${foo + 1}"
Unit is a placeholder return value for functions that don't actually return a meaninfgul value. It is the default return type for any function if one is not specified explicitly. Nothing is a placeholder return value for functions that should never actually return (ie they throw an exception or have infinite loop). Good SO discussion on differences.
var a: String = “abc”
a = null // won’t compile, can't assign null
var b: String? = “abc”
b = null // ok
b.length // error, could be null
b?.length // ok, since you're explicit that it could be null
safe calls can be chained and the chain will return null if any of the properties are null, no null errors raised
bob?.department?.head?.name
elvis operator checks for null values
val length = b?.length ?: -1
you can ask for a NullPointerException explicitly with !! operator, if you really really want to...
val length = b!!.length
can also cast the variable to make it no longer nullable
val aInt: Int? = a as? Int
null items in a collection
val nullableList: List<Int?> = listOf(1, 2, null, 4)
val intList: List<Int> = nullableList.filterNotNull()
used for lambda expressions + anonymous functions
max(strings, { a, b -> a.length < b.length })
// equivalent forms:
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
val sum = { x: Int, y: Int -> x + y }
lambda expression:
- surrounded by curly braces
- parameters go inside curly braces, have optional type annotations
- function body goes after arrow
->
- last expression is treated as return value
- return type is inferred from last expression
while kotlin does have a map collection that works like standard hashes or dictionaries in other languages, a preferred method is to use data classes, which creates an interface of the keys you're expecting and the compiler adds handy methods like equals()
, toString()
and copy()
for you automatically
data class Person(val name: String, val age: Int){}
// can set default values
data class User(val name: String = "", val age: Int = 0)
more info on classes
// name is a primary constructor parameter
class Customer(name: String) {
val customerKey = name.uppercase()
}
class Person(
val firstName: String,
val lastName: String,
var isEmployed: Boolean = true
var age: Int, // trailing comma
) { /*...*/ }
more info on properties
can have read-only properties with val
keyword or mutable properties with var
keyword
val simple: Int? // has type Int, default getter, must be initialized in constructor
val inferredType = 1 // has type Int and a default getter
can implement custom getters and setters. setters are called every time you assign a value to the property, except its initialization.
class Rectangle(val width: Int, val height: Int) {
val area: Int // property type is optional since it can be inferred from the getter's return type
get() = this.width * this.height
// same as above, shorter without specifying type
val area get() = this.width * this.height
var stringRepresentation: String
get() = this.toString()
set(value) { // convention to use "value" as the name of the setter parameter
setDataFromString(value) // parses the string and assigns values to other properties
}
}
lateinit is used to specify not-null properties after object initilization for things like unit tests or dependency injections.
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // dereference directly
}
}
sealed classes allow you to restrict which classes are allowed to subclass the current class, so you know them all at compile time. commonly used for error classes, sealed classes are abstract and therefore cannot be instantiated directly.
extension functions allow you to bolt a function onto an existing type. this
inside the function refers to the receiver object, passed before the dot.
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' corresponds to the list
this[index1] = this[index2]
this[index2] = tmp
}