Classes and Objects
Classes
Section titled “Classes”Classes in Kotlin are declared with the class keyword. They are final by default — use open to Allow inheritance.
class User(val name: String, var age: Int)This single line declares a class with a primary constructor, two properties (name is read-only, age is mutable), and no body.
Primary Constructor
Section titled “Primary Constructor”The primary constructor is declared in the class header. Constructor parameters can also declare Properties.
class Person( val firstName: String, val lastName: String, var email: String) { val fullName: String get() = "$firstName $lastName"
init { require(email.contains("@")) { "Invalid email: $email" } }}The init block is the primary constructor body. Multiple init blocks execute in declaration Order.
Secondary Constructors
Section titled “Secondary Constructors”class Person(val name: String) { var age: Int = 0
constructor(name: String, age: Int) : this(name) { this.age = age }}Every secondary constructor must delegate to the primary constructor (or another secondary Constructor that eventually delegates to the primary). This ensures properties are initialized Exactly once.
Visibility Modifiers
Section titled “Visibility Modifiers”| Modifier | Class Member | Top-Level Declaration |
|---|---|---|
public | Visible everywhere (default) | Visible everywhere (default) |
private | Inside class only | Inside file only |
protected | Inside class + subclasses | N/A |
internal | Inside module | Inside module |
A module is a set of Kotlin files compiled together — a Gradle project, a Maven project, or an IntelliJ module.
Properties
Section titled “Properties”Kotlin properties combine fields, getters, and setters into a single declaration.
class Rectangle(val width: Double, val height: Double) { val area: Double get() = width * height
var perimeter: Double = 0.0 private set
init { perimeter = 2 * (width + height) }}Backing fields are generated automatically when a getter or setter references the property name. Access them with the field identifier:
var counter: Int = 0 set(value) { if (value >= 0) field = value }Computed Properties
Section titled “Computed Properties”Properties without a backing field compute their value on every access.
class Polygon(val sides: List<Point>) { val perimeter: Double get() = sides.zipWithNext().sumOf { (a, b) -> sqrt((b.x - a.x).pow(2) + (b.y - a.y).pow(2)) }}Late-Initialized Properties
Section titled “Late-Initialized Properties”class Service { lateinit var config: Config
fun isReady(): Boolean = ::config.isInitialized}lateinit bypasses null safety. Use it only when initialization is guaranteed to happen before First access, in frameworks that call lifecycle methods after construction.
Inheritance
Section titled “Inheritance”Classes are final by default. Mark a class as open to allow subclassing.
open class Animal(val name: String) { open fun sound(): String = "..."}
class Dog(name: String) : Animal(name) { override fun sound(): String = "Woof"}
class Cat(name: String) : Animal(name) { override fun sound(): String = "Meow"}Abstract Classes
Section titled “Abstract Classes”abstract class Shape { abstract val area: Double abstract fun draw()
fun describe(): String = "Shape with area $area"}
class Circle(val radius: Double) : Shape() { override val area: Double get() = PI * radius * radius override fun draw() { /* ... */ }}Interfaces
Section titled “Interfaces”interface Clickable { fun click() fun showOff() = println("Clickable!") // default implementation}
interface Focusable { fun showOff() = println("Focusable!")}
class Button : Clickable, Focusable { override fun click() { /* ... */ }
override fun showOff() { super<Clickable>.showOff() super<Focusable>.showOff() }}Data Classes
Section titled “Data Classes”Data classes are designed to hold data. The compiler generates equals()``hashCode() toString()``componentN() functions, and a copy() method.
data class User( val id: Long, val name: String, val email: String, val active: Boolean = true)val user = User(1, "Alice", "alice@example.com")val copied = user.copy(name = "Bob", email = "bob@example.com")
val (id, name, email, active) = user // destructuring
println(user) // User(id=1, name=Alice, email=alice@example.com, active=true)Requirements and Restrictions
Section titled “Requirements and Restrictions”- The primary constructor must have at least one parameter.
- All primary constructor parameters must be
valorvar. - Data classes cannot be
open``abstract``innerOrsealed. - The generated
equals()compares all primary constructor properties structurally.
Copy Semantics
Section titled “Copy Semantics”copy() performs a shallow copy. Mutable properties in the copy reference the same objects as the Original.
data class Config(val tags: MutableList<String>)
val original = Config(mutableListOf("a", "b"))val modified = original.copy(tags = original.tags.apply { add("c") })// original.tags now contains ["a", "b", "c"] -- mutation leakedSealed Classes
Section titled “Sealed Classes”Sealed classes restrict inheritance to a finite set of subclasses declared in the same package. This Enables exhaustive when expressions.
sealed class Result<out T> { data class Success<T>(val value: T) : Result<T>() data class Failure(val error: Throwable) : Result<Nothing>() data object Loading : Result<Nothing>()}
fun handle(result: Result<String>) { when (result) { is Result.Success -> println("Got: ${result.value}") is Result.Failure -> println("Error: ${result.error.message}") is Result.Loading -> println("Loading...") }}In Kotlin 1.5+, sealed class subclasses can be defined in the same package but not necessarily the Same file. In Kotlin 1.7+, sealed interfaces are supported.
Enum Classes
Section titled “Enum Classes”enum class Direction(val degrees: Int) { NORTH(0), EAST(90), SOUTH(180), WEST(270);
fun turnedRight(): Direction = entries[(ordinal + 1) % entries.size] fun turnedLeft(): Direction = entries[(ordinal + 3) % entries.size]}entries (Kotlin 1.9+) returns all enum constants. Use entries instead of the deprecated values().
Enum with Abstract Methods
Section titled “Enum with Abstract Methods”enum class PaymentMethod { CASH { override fun process(amount: Double) = "Paid $amount in cash" }, CARD { override fun process(amount: Double) = "Charged $amount to card" }, CRYPTO { override fun process(amount: Double) = "Transferred $amount in crypto" };
abstract fun process(amount: Double): String}Object Declarations
Section titled “Object Declarations”object creates a singleton. Initialization is thread-safe.
object DatabaseConfig { val url = System.getenv("DB_URL") ?: "jdbc:postgresql://localhost:5432/app" val poolSize = System.getenv("DB_POOL_SIZE")?.toIntOrNull() ?: 10 val timeout = 30_000L}Object declarations can implement interfaces:
object ConsoleLogger : Logger { override fun log(level: Level, message: String) { println("[$level] $message") }}Anonymous Objects
Section titled “Anonymous Objects”Anonymous objects replace Java anonymous inner classes. They can implement interfaces or extend Classes.
val listener = object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { println("Clicked at (${e.x}, ${e.y})") }}Anonymous objects used as local variables have an anonymous type. If you need to pass the object Outside its declaration scope, declare the type explicitly or use an interface.
Companion Objects
Section titled “Companion Objects”A companion object is an object declaration tied to a class. Members declared in it are accessed via The class name — analogous to Java static members.
class User private constructor( val name: String, val email: String) { companion object Factory { fun create(name: String, email: String): User { return User(name, email) }
fun fromJson(json: String): User { // parse and construct } }}
val user = User.create("Alice", "alice@example.com")val user2 = User.fromJson("""{"name": "Bob", "email": "bob@example.com"}""")The companion object can have a name (e.g., Factory) or use the default name Companion. A class Can have only one companion object.
Companion objects can implement interfaces:
interface Factory<T> { fun create(): T}
class User(val name: String) { companion object : Factory<User> { override fun create(): User = User("default") }}
fun <T> spawn(factory: Factory<T>): T = factory.create()val user = spawn(User) // User(name=default)Common Pitfalls
Section titled “Common Pitfalls”- ** Forgetting that classes are final by default. Mark classes and methods as
openwhen designing for inheritance. - ** Using
data classfor mutable entities.equals()andhashCode()include mutable properties, which breaks the contract if the object is mutated after being placed in aHashSetorHashMap. - ** Storing references to mutable collections in data classes. The
copy()method performs a shallow copy, so mutations to the copy”s collections affect the original. - ** Using
objectdeclarations for dependency injection. Singletons make testing harder because they cannot be replaced or mocked. Prefer constructor injection with regular classes. - ** Confusing
objectdeclarations with anonymous objects.objectcreates a named singleton;object : SomeInterface { ... }creates an anonymous instance.
Intuition
Section titled “Intuition”Classes are blueprints for creating objects with shared behavior and state. Kotlin’s classes are final by default, encouraging composition over inheritance. Data classes reduce boilerplate for simple data holders, while sealed classes define restricted type hierarchies for exhaustive pattern matching. Companion objects provide a place for factory methods and class-level logic without Java’s static keyword. Object declarations create singletons, and class delegation implements interfaces by forwarding to another object.
Summary
Section titled “Summary”This topic covers the core concepts of classes and objects, including underlying theory, practical implementation, and key applications.
Key concepts include:
- core concepts and terminology
- algorithms and computational thinking
- practical implementation
- security and ethical considerations
- applications in the real world
Understanding these concepts thoroughly is essential for both examinations and practical programming, and requires both theoretical knowledge and hands-on practice.
Worked Examples
Section titled “Worked Examples”Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.