Classes, inheritance, objects and sealed classes
Primary constructors and init, open and override, object and companion object, interfaces with defaults, and sealed classes with when.
Kotlin's class syntax is short, and that is exactly why screenings test it: a missing open or a misplaced companion is easy to spot in a snippet. This lesson covers the class features Android and backend code use every day.
Constructors, properties and inheritance
open class Account(val id: Int, var balance: Int) {
open fun describe() = "Account $id has $balance"
}
class Savings(id: Int, balance: Int, val rate: Double) : Account(id, balance) {
override fun describe() = super.describe() + " at $rate%"
}
val s = Savings(7, 500, 4.5)
println(s.describe()) // Account 7 has 500 at 4.5%
val a: Account = s
println(a.describe()) // same line: the override runsWriting val or var in the primary constructor creates a property; a bare parameter such as id in Savings is only visible in property initialisers and init blocks, which run in order when the object is created. Classes and functions are final by default, so both the parent class and the method need open before a subclass can override. Calling through a parent-typed reference still runs the override.
object and companion object
object Config {
const val MAX_RETRIES = 3
}
class User private constructor(val name: String) {
companion object {
fun create(raw: String) = User(raw.trim())
}
}
println(User.create(" Asha ").name) // AshaAn object is a singleton created on first use. A companion object holds members called on the class name, which is how Kotlin does factory methods and constants without static. The private constructor forces callers through create, a common validation pattern.
Sealed classes and when
sealed class Result {
data class Success(val rows: Int) : Result()
data class Failure(val reason: String) : Result()
object Loading : Result()
}
fun message(r: Result): String = when (r) {
is Result.Success -> "Loaded ${r.rows} rows"
is Result.Failure -> "Failed: ${r.reason}"
Result.Loading -> "Please wait"
}
println(message(Result.Success(12))) // Loaded 12 rowsA sealed class lists all its subclasses in one file, so when used as an expression is checked for completeness: add a fourth subclass and the compiler flags every when that forgot it. Inside the is branch r is smart-cast, so r.rows needs no cast. This is the standard way to model loading, success and error states.
Common mistakes and a practice habit
- Forgetting
openand getting "this type is final". - Expecting two instances of a normal class to be equal with
==. Only data classes compare by fields;Savings(1, 10, 1.0) == Savings(1, 10, 1.0)is false. - Leaving out
elsein awhenexpression over a non-sealed type.
Practice habit: Model one screen you know, such as a login form, as a sealed class with three states, then write a when that returns the text for each state. Add a fourth state and watch the compiler point out every place to update. Doing this once a week makes sealed classes second nature.
More in Programming