Classes and Objects
|
This section documents Kotlin 2.4.x on the JVM, as published at kotlinlang.org, which is the reference these pages are written and verified against. This content was generated with the assistance of AI and should be verified against kotlinlang.org before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Kotlin classes fold field declarations, a getter, a setter, and constructor parameters into one concise declaration wherever the intent is a plain data holder — with everything Java-style, fully explicit, still available when needed.
The Primary Constructor
Constructor parameters that also become properties are declared directly in the class header:
class Person(val name: String, var age: Int)
val ada = Person("Ada", 30)
println(ada.name) // "Ada" -- name is a read-only property (val)
ada.age = 31 // age is mutable (var)
This one line replaces what Java would spell out as two fields, a constructor, and (if using getters/setters at
all) two accessor methods. A primary-constructor parameter without val/var is just a plain constructor
parameter, not a property, and is only usable inside an init block or another property initializer.
Secondary Constructors and init Blocks
A class can declare additional constructors with constructor, each of which must ultimately delegate to the
primary constructor via this(…). init blocks run, in declaration order interleaved with property
initializers, as part of the primary constructor’s execution — the place to put validation or derived setup
that a one-line parameter list cannot express:
class Rectangle(val width: Double, val height: Double) {
val area: Double // property with no initializer here...
init {
require(width > 0 && height > 0) { "dimensions must be positive" }
area = width * height // ...assigned inside init
}
constructor(side: Double) : this(side, side) // secondary constructor -- delegates to the primary
}
val square = Rectangle(4.0) // uses the secondary constructor; area == 16.0
Properties: Custom Getters, Setters, and Backing Fields
Any property can replace its default accessor with a custom one. A backing field, referenced as field
inside the accessor, is generated automatically whenever an accessor actually reads or writes the property’s
stored value:
class Temperature(celsiusInitial: Double) {
var celsius: Double = celsiusInitial
set(value) {
field = value // "field" is the auto-generated backing field
println("celsius set to $value")
}
val fahrenheit: Double // a computed, read-only property -- no backing field at all
get() = celsius * 9 / 5 + 32
}
val t = Temperature(20.0)
println(t.fahrenheit) // 68.0 -- recomputed on every access
t.celsius = 25.0 // triggers the custom setter, prints "celsius set to 25.0"
A property with no custom accessor at all is exactly the field + getter (+ setter, for var) pair a Java
record/POJO would hand-write.
Visibility Modifiers
| Modifier | Visible from |
|---|---|
|
everywhere — Kotlin’s default, unlike Java’s package-private default. |
|
anywhere in the same Gradle/Maven module — Kotlin’s addition, with no Java equivalent; the closest Java analogue, package-private, is module-agnostic and visible to any class in the same package regardless of which JAR it comes from. |
|
the declaring class and its subclasses (not the whole package, unlike Java’s |
|
the declaring class only (or, for a top-level declaration, the containing file only). |
Classes Are final by Default
Unlike Java, where any non-final class can be subclassed, every Kotlin class is implicitly final — inheriting from it requires the base class to be explicitly marked open:
class Base // cannot be subclassed
open class OpenBase // can be subclassed -- see Inheritance and Interfaces
// class Derived : Base() // compile error: Base is final
class Derived : OpenBase() // fine
This default exists to make inheritance a deliberate, opt-in design decision rather than an accident of
omitting final — and it is why frameworks that need to subclass application classes behind the scenes (most
notably Spring’s CGLIB-proxied @Configuration/@Service beans) require an "all-open" compiler plugin; see
Inheritance and Interfaces and
Java or Kotlin for Spring Boot? for how that plugin resolves it.
See Also
-
Inheritance and Interfaces —
open,override, and the all-open plugin. -
Data Classes and Destructuring — classes whose entire purpose is holding data.
-
Objects and Companion Objects — static-like members without a
statickeyword.