Constructors and init Blocks

Primary Constructor

The primary constructor is part of the class header. Parameters can have default values, so callers can skip them, and callers can use named arguments.

Example

class User(val name: String, val city: String = "Hyderabad", val isPremium: Boolean = false)

fun main() {
    val a = User("Arjun")
    val b = User("Meera", "Pune")
    val c = User(name = "Kiran", isPremium = true)
    println("${a.name} - ${a.city} - ${a.isPremium}")
    println("${b.name} - ${b.city} - ${b.isPremium}")
    println("${c.name} - ${c.city} - ${c.isPremium}")
}

Output

Arjun - Hyderabad - false
Meera - Pune - false
Kiran - Hyderabad - true

init Blocks

The primary constructor cannot contain code. Put setup logic and validation in an init block. It runs every time an object is created, in the order it appears in the class (together with property initializers).

Example

class BankAccount(val holder: String, initialBalance: Double) {
    val balance: Double
    val accountType: String

    init {
        require(initialBalance >= 0) { "Balance cannot be negative" }
        balance = initialBalance
        println("init 1: account created for $holder")
    }

    init {
        accountType = if (balance >= 100000) "Premium" else "Regular"
        println("init 2: type is $accountType")
    }
}

fun main() {
    val acc = BankAccount("Ravi", 150000.0)
    println("${acc.holder} has ${acc.balance}")
}

Output

init 1: account created for Ravi
init 2: type is Premium
Ravi has 150000.0

Secondary Constructors

A class can have extra constructors written with the constructor keyword. If a primary constructor exists, every secondary constructor must call it with this(...). In modern Kotlin, default arguments usually replace secondary constructors; you still see them in Android custom views and Java interop.

Example

class Product(val name: String, val price: Double, val category: String) {
    constructor(name: String, price: Double) : this(name, price, "General") {
        println("Secondary constructor used for $name")
    }

    init {
        println("init: $name created")
    }
}

fun main() {
    val p1 = Product("Laptop", 55000.0, "Electronics")
    val p2 = Product("Notebook", 40.0)
    println("${p1.name} -> ${p1.category}")
    println("${p2.name} -> ${p2.category}")
}

Output

init: Laptop created
init: Notebook created
Secondary constructor used for Notebook
Laptop -> Electronics
Notebook -> General

Note: Notice the order in the output: the init block runs first (as part of the primary constructor), then the body of the secondary constructor.

Exercise

Exercise: Create a class Rectangle(width, height) whose init block rejects non-positive sizes with require, and a secondary constructor Rectangle(side) that makes a square.

Show answer
class Rectangle(val width: Int, val height: Int) {
    init {
        require(width > 0 && height > 0) { "Sizes must be positive" }
    }
    constructor(side: Int) : this(side, side)

    fun area() = width * height
}

fun main() {
    println(Rectangle(4, 5).area()) // 20
    println(Rectangle(3).area())    // 9
}