Constructors with default values
The primary constructor is part of the class header. Secondary constructors are declared in the class body.
class User constructor(nickname: String) {
init {
// code to execute on Class creation
}
}
// using default values
class User(val nickname: String, val isSubscribed: Boolean = true)
Secondary Constructors
If a class has a primary constructor, each secondary constructor needs to delegate to the primary constructor
class Person(val name: String) {
constructor(name: String, parent: Person): this(name) {
parent.children.add(this)
}
}
Properties
These can be declared as mutable, using the var
keyword or read-only using val
. To use a property we refer to it by name, as if it were a field in Java
class Address {
var name: String = ...
var street: String = ...
var city: String = ...
var state: String? = ...
}
Declare and init propertiers from the primary constructor
class Person(
val firstName: String,
val lastName: String,
var age: Int) {
}
Visibility Modifiers
If you omit a modifier, the default visibility in Kotlin becomes public. Declarations are also final by default.
A constructor can also be private for Singleton or Factory methods
class C private constructor(a: Int) {
...
}
Data classes
Data classes are intented for types that are meant to be data containers and nothing more.
They avoid the need to define the methods equals()
hashCode()
toString()
data class Customer(val id: Int, val name: String, var address: String)
It’s strongly recommended that you use read-only properties, making the instances of the data class immutable.
Sealed classes
They’re used for representing restricted class hierarchies. A sealed class is an abstract class that restricts the possibility of creating subclasses. They’re the solution when you want a class that cannot be extended.
It’s a rather more powerful Enum option as unlike Enum, the derived classes of a sealed class can have many instances.
// declaration
sealed class Expr
data class Const(val number: Double): Expr()
data class Sum(val e1: Expr, val e2: Expr): Expr()
Object NotANuber: Expr()
// usage
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
}
Singletons
They’re way easier than in Java
// you can call Singleton.doSomething() and each time
// you'll see the counter increasing.
object Singleton {
private var count = 0
fun doSomething(): Unit {
println("Do something with count ${++count}")
}
}
The Object
keyword is used to declare a class and create an instance of a class at the same time.
Companion Objects
Classes in Kotlin cannot have static members. If you need something like a static function that needs access to the internal members of a class, you can write it as a companion object inside that class.
class A {
// the companion's name can be omitted
companion object {
fun bar() {
println("Companion object called")
}
}
}
// invoke
A.bar()
factory design pattern example
interface StudentFactory {
fun create(name: String): Student
}
class Student private constructor(val name: String) {
companion object: StudentFactory {
override fun create(name: String): Student {
return Student(name)
}
}
}
// invoke
Student.create("Mario C.")
Destructuring Declarations
Sometimes it’s convenient to destructure an object into a number of variables. This creates multiple variables at once.
val (name, age) = person
println(name)
println(age)
Returning two values from a function
A compact way of returning two values from a function in Kotlin is to declare a data class and return its instance
data class Result(val result: Int, val status: Status)
fun function(...): Result {
// computations
return Result(result, status)
}
// invoke
val (result, status) = function(...)
Collections and for-loops
Iterating over a collection with for-loops can be done via destructuring declarations
// this is an example for a Map
var map: HashMap<Int, Person> = HashMap()
map.put(1, person)
for ((key, value) in map) {
println("Key: $key, value: $value")
}
Underscore and destructuring in Lambdas
In case we don’t need all values obtained in a destructuring declaration, we use underscore instead of the variable name.
val (_, name, age) = person
// if the fields we don't need are at the end, we just omit them
val (id, name) = person
destructuring in lambdas
we can also use the destructuring declarations syntax for lambda parameters. But be aware of the difference between Kotlin’s declare two parameters and declare a destructuring pair.
{ a -> ... } // one parameter
{ a, b -> ... } // two parameters
{ (a, b) -> ... } // a destructured pair
{ (a, b), c -> ... } // a destructured pair and another param.