Kotlin’s object model is concise and expressive — primary constructors, data classes that generate boilerplate for you, object singletons, and sealed classes that make illegal states unrepresentable.
Kotlin declares a class and its constructor together in one compact line — the primary constructor lists properties right in the header, and val/var there declares and initializes them. Default arguments mean you rarely need multiple constructors. This collapses the field-plus-constructor-plus-getter ceremony of Java into a single declaration.
// properties declared right in the constructor — no boilerplate
class Person(val name: String, var age: Int = 0) {
// a member function
fun birthday() { age++ }
// a computed property
val isAdult: Boolean get() = age >= 18
}
fun main() {
val p = Person("Ada", 17)
p.birthday()
println("${p.name} is ${p.age}, adult=${p.isAdult}") // Ada is 18, adult=true
}Marking a class data tells Kotlin to generate equals, hashCode, toString, and copy from the constructor properties — the tedious value-type boilerplate you write by hand in Java. Data classes are how you model the data your app passes around (API responses, UI state), and copy makes it easy to derive a modified version while keeping the original immutable.
data class User(val id: Int, val name: String, val active: Boolean = true)
fun main() {
val u = User(1, "Ada")
println(u) // User(id=1, name=Ada, active=true) <- toString
val u2 = User(1, "Ada")
println(u == u2) // true <- value equality (generated equals)
val deactivated = u.copy(active = false) // copy with one field changed
println(deactivated) // User(id=1, name=Ada, active=false)
// copy keeps the original immutable — the basis of immutable UI state.
}The object keyword declares a singleton — a class with exactly one instance — in one word, ideal for stateless helpers or a single shared resource. A companion object inside a class holds members tied to the class itself rather than an instance, which is Kotlin’s replacement for Java static members, commonly used for factory functions and constants.
// a singleton — one instance, created lazily on first use
object Logger {
fun log(msg: String) = println("[LOG] $msg")
}
class User private constructor(val name: String) {
companion object { // members tied to the class itself
const val TABLE = "users"
fun create(name: String) = User(name) // factory function
}
}
fun main() {
Logger.log("started") // Logger.log — no instance needed
val u = User.create("Ada") // call via the class name
println("${u.name} in ${User.TABLE}")
}Enums model a fixed set of constants; sealed classes model a fixed set of types, each able to carry its own data. A sealed hierarchy lets a when expression be exhaustive — the compiler knows every case, so you cannot forget one, and adding a case forces you to handle it everywhere. This is how Kotlin models states like Loading/Success/Error and makes illegal states unrepresentable.
enum class Direction { NORTH, SOUTH, EAST, WEST }
// a sealed hierarchy: a closed set of types, each with its own data
sealed class Result
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
object Loading : Result()
fun render(r: Result): String = when (r) { // exhaustive — no 'else' needed
is Success -> "Data: ${r.data}" // smart-cast to Success
is Error -> "Failed: ${r.message}"
Loading -> "Loading..."
}
// add a new Result subtype and every 'when' stops compiling until you handle it.
fun main() = println(render(Success("hi")))