How it works
The explanation, with examples you can run.
val, var and immutability
val declares a read-only reference; var a mutable one. Neither says anything about the object referred to. A val holding a MutableList can still be appended to. Kotlin's collection interfaces split the two roles: List has no add, MutableList does. Exposing List is how a class keeps a mutable list to itself:
class Cart { private val _items = mutableListOf<String>() val items: List<String> get() = _items // callers see a read-only view fun add(item: String) { _items += item }}A read-only view isn't a copy: if you need a snapshot, return _items.toList().
Data classes
data class Money(val cents: Long, val currency: String) { var note: String = "" // not part of equals/hashCode/toString/copy} fun main() { val a = Money(500, "EUR").apply { note = "lunch" } val b = Money(500, "EUR") println(a == b) // true: only constructor properties count println(a) // Money(cents=500, currency=EUR) val (cents, currency) = a // component1(), component2() println("$cents $currency") // 500 EUR println(a.copy(cents = 750)) // Money(cents=750, currency=EUR)}data generates equals, hashCode, toString, copy and component1..N from the primary constructor properties only. copy is shallow. Data classes can't be open, abstract or sealed, which keeps equals symmetric.
== calls equals; === compares references. For a class without data (or your own equals), == is reference equality.
object and companion object
An object declaration defines a class and its single instance at once. On the JVM the instance is created in the class's static initialiser, so it is lazy (on first access) and thread-safe without any locking code of yours:
object Clock { var offsetMillis = 0L fun now() = System.currentTimeMillis() + offsetMillis} class User private constructor(val name: String) { companion object { fun named(name: String): User = User(name.trim()) // a factory }} fun main() { println(User.named(" Ada ").name) // Ada println(Clock.now() > 0) // true}A companion object is one such object nested in a class, reachable through the class name. It is a real object: it can implement interfaces and hold state, and from Java you reach it as User.Companion.named(...) unless you mark the function @JvmStatic. An object expression (object : Runnable { ... }) creates an anonymous one-off instance instead, like Java's anonymous classes.
Two other kinds are worth knowing: a value class wraps a single value with no allocation at runtime in most places (@JvmInline value class UserId(val raw: String)), and an enum class has a fixed set of instances. Sealed classes, the closed hierarchies used with when, have their own topic.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.