constructor și init în Kotlin

Clase și obiecte Kotlin 1.0

constructor declară un constructor secundar (sau unul primar cu modificatori); blocurile init rulează la crearea fiecărui obiect.

Sintaxă
class Nume(val a: Tip) {             // constructorul primar
    init { /* rulează la creare */ }

    constructor(b: Tip2) : this(conversie(b)) { ... }   // constructor secundar
}

class Ascuns private constructor(val x: Int)

Ce face constructor și init

Constructorul primar se scrie în antetul clasei: class Elev(val nume: String). El nu are corp, așa că codul care trebuie să ruleze la crearea obiectului (validări, calcule) se pune în blocuri init { }. Blocurile init și inițializările proprietăților rulează în ordinea în care apar în clasă.

Un constructor secundar se declară în corpul clasei cu constructor(...). Dacă clasa are constructor primar, cel secundar trebuie să-l apeleze cu : this(...). Corpul constructorului secundar rulează după toate blocurile init. În Kotlin ai nevoie rar de constructori secundari: valorile implicite ale parametrilor rezolvă de obicei aceeași problemă.

Cuvântul constructor se scrie și în antet când constructorul primar are un modificator sau o adnotare: class Conexiune private constructor(...). Un constructor privat obligă codul din afară să folosească o funcție de creare (de obicei în companion object).

Exemple

Exemplul 1: Ordinea: proprietăți și blocuri init

Mesajele arată ordinea reală: inițializările și blocurile init rulează de sus în jos, la fiecare obiect creat.

class Elev(val nume: String, nota: Int) {
    val notaCorectata = nota.coerceIn(1, 10).also { println("1. calculez nota pentru $nume") }

    init {
        println("2. primul init: $nume are nota $notaCorectata")
    }

    val promovat = (notaCorectata >= 5).also { println("3. calculez dacă $nume a promovat") }

    init {
        println("4. al doilea init: promovat = $promovat")
    }
}

fun main() {
    Elev("Ana", 12)
    println("---")
    Elev("Radu", 4)
}

Rezultat

1. calculez nota pentru Ana
2. primul init: Ana are nota 10
3. calculez dacă Ana a promovat
4. al doilea init: promovat = true
---
1. calculez nota pentru Radu
2. primul init: Radu are nota 4
3. calculez dacă Radu a promovat
4. al doilea init: promovat = false

Exemplul 2: Validare în init

require din init oprește crearea unui obiect cu date greșite.

class Temperatura(val grade: Double) {
    init {
        require(grade >= -273.15) { "Sub zero absolut: $grade" }
    }
}

fun main() {
    println(Temperatura(21.5).grade)
    try {
        Temperatura(-300.0)
    } catch (e: IllegalArgumentException) {
        println("Eroare: ${e.message}")
    }
}

Rezultat

21.5
Eroare: Sub zero absolut: -300.0

Exemplul 3: Constructor secundar și constructor privat

Constructorul secundar primește un text de forma „Ana, 21” și apelează constructorul primar cu this(...). Factura are constructorul privat, deci se creează doar prin Factura.noua.

class Persoana(val nume: String, val varsta: Int) {
    constructor(text: String) : this(text.substringBefore(",").trim(), text.substringAfter(",").trim().toInt()) {
        println("Creată din text: \"$text\"")
    }
}

class Factura private constructor(val numar: String) {
    companion object {
        private var urmatorul = 1
        fun noua() = Factura("F-%04d".format(urmatorul++))
    }
}

fun main() {
    val p = Persoana("Ana Pop, 21")
    println("${p.nume} are ${p.varsta} ani")
    println(Persoana("Mihai", 30).nume)

    println(Factura.noua().numar)
    println(Factura.noua().numar)
}

Rezultat

Creată din text: "Ana Pop, 21"
Ana Pop are 21 ani
Mihai
F-0001
F-0002

Greșeli frecvente

  • Constructor secundar care nu apelează constructorul primar: eroare primary constructor call expected. Adaugă : this(...).
  • Folosirea unei proprietăți într-un init scris înaintea ei: proprietatea nu este încă inițializată (eroare variable 'b' must be initialized).
  • Mulți constructori secundari pentru variante de apel: de obicei e mai simplu cu valori implicite: class Elev(val nume: String, val clasa: String = "a IX-a").

Comenzi înrudite

  • class: class declară o clasă: un tip nou care grupează date (proprietăți) și comportament (metode).
  • this: this înseamnă obiectul curent: obiectul pe care s-a apelat metoda sau receptorul unei funcții de extensie.
  • companion object: companion object ține membrii care aparțin clasei, nu fiecărui obiect: constante, funcții de creare, contoare comune.
  • require: Verifică argumentele unei funcții și aruncă IllegalArgumentException dacă nu sunt bune; requireNotNull cere o valoare diferită de null.
  • lateinit: lateinit permite unei proprietăți var ne-nullable să primească valoare mai târziu, după crearea obiectului.