Reading — step 1 of 7
Learn
Kotlin classes are concise. The primary constructor is part of the class header; properties are declared inline. Kotlin in Action treats this as foundational — most Kotlin types are classes, and the syntax pays back from line one.
Primary constructor
class Person(val name: String, var age: Int)
That's a complete class. The constructor parameters are simultaneously property declarations:
val name: String— read-only property + ctor parametervar age: Int— mutable property + ctor parameter
Use it:
val ada = Person("Ada", 36)
println(ada.name) // Ada
ada.age = 37 // OK — var
// ada.name = "Linus" // ERROR — val
No new keyword. No separate field declarations. No boilerplate getters/setters.
init blocks — initialization logic
class User(val name: String, age: Int) {
val displayName: String
init {
require(age >= 0) { "age must be non-negative" }
displayName = if (name.isBlank()) "Anonymous" else name
}
}
init runs as part of the primary constructor. Multiple init blocks run in order.
Note: age here doesn't have val/var — it's just a constructor parameter, not a property.
Secondary constructors
class Rectangle(val width: Int, val height: Int) {
constructor(side: Int) : this(side, side) // delegates to primary
}
val sq = Rectangle(side = 5) // 5x5
val r = Rectangle(width = 4, height = 6)
Secondary constructors must call the primary (directly or transitively) via this(...). Allows alternative initialization paths.
Default arguments and named parameters
class HttpClient(
val baseUrl: String,
val timeoutMs: Int = 5000,
val retries: Int = 3
)
val client = HttpClient("https://api.example.com") // uses defaults
val custom = HttpClient("https://api.example.com", retries = 5) // named
Most Kotlin code avoids constructor overload chains — defaults + named args replace them.
Properties with custom getters/setters
class Temperature(celsius: Double) {
var celsius: Double = celsius
set(value) {
require(value >= -273.15) { "below absolute zero" }
field = value // 'field' is the backing field
}
val fahrenheit: Double
get() = celsius * 9 / 5 + 32
}
field is the magic backing-field reference inside accessors. fahrenheit has only a getter (computed on access).
visibility modifiers
public(default) — visible everywhereinternal— visible within the same moduleprotected— only this class and subclassesprivate— only this class (or this file for top-level)
class BankAccount {
private var balance: Double = 0.0
fun deposit(amount: Double) { balance += amount }
fun getBalance(): Double = balance
}
Final by default
Classes and methods are final (closed for inheritance) unless marked open. The opposite of Java. Encourages composition over inheritance.
open class Animal {
open fun speak() = "generic sound"
}
class Dog : Animal() { // notice the parens — calling Animal's primary ctor
override fun speak() = "woof"
}
Common mistakes
- Forgetting
val/varon constructor parameters — without it, the parameter ISN'T a property, just a ctor param. Surprising when you can't access it later. - Calling parent constructor with wrong syntax —
class Dog : Animal()has parens; notclass Dog : Animal. - Missing
openand trying to subclass or override — Kotlin defaults to final. Need explicit open on the parent. - Trying to use
privateto hide from subclasses — useprotectedfor that. - Using
initto set vals declared in primary — already happened; redundant.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…