open și final în Kotlin

În Kotlin clasele și metodele sunt final implicit; open permite moștenirea sau suprascrierea, iar final o oprește din nou.

Sintaxă
open class Baza {
    open fun poateFiSuprascrisa() { }
    fun nuPoateFiSuprascrisa() { }
}

class Derivata : Baza() {
    final override fun poateFiSuprascrisa() { }
}

Ce face open și final

Spre deosebire de Java, în Kotlin o clasă nu poate fi moștenită și o metodă nu poate fi suprascrisă decât dacă o permiți explicit cu open. Implicit, totul este final. Așa clasele nu se schimbă pe neașteptate din cauza unei subclase la care autorul nu s-a gândit.

Moștenirea se scrie cu : și apelul constructorului clasei de bază: class Carte(...) : Produs(...). O metodă open se suprascrie cu override. Un membru marcat override este la rândul lui open pentru subclasele următoare; dacă vrei să oprești lanțul, scrii final override.

final scris explicit pe o clasă sau o metodă obișnuită este redundant (este deja implicit), dar are sens pe override. Clasele abstract și interfețele sunt deschise implicit.

Exemple

Exemplul 1: Clasă open cu metodă open

Produs și pretFinal sunt open, deci Carte le poate moșteni și schimba. eticheta nu este open, dar folosește versiunea potrivită a lui pretFinal.

open class Produs(val nume: String, val pret: Double) {
    open fun pretFinal() = pret * 1.21
    fun eticheta() = "$nume: %.2f lei".format(pretFinal())
}

class Carte(nume: String, pret: Double) : Produs(nume, pret) {
    override fun pretFinal() = pret * 1.11
}

fun main() {
    val produse = listOf(Produs("Lampă", 100.0), Carte("Moromeții", 100.0))
    produse.forEach { println(it.eticheta()) }
}

Rezultat

Lampă: 121.00 lei
Moromeții: 111.00 lei

Exemplul 2: final override oprește suprascrierea

Vehicul → Masina → Taxi. Masina marchează roti cu final override, deci Taxi mai poate schimba doar descriere.

open class Vehicul {
    open fun roti() = 0
    open fun descriere() = "vehicul cu ${roti()} roți"
}

open class Masina : Vehicul() {
    final override fun roti() = 4
    override fun descriere() = "mașină cu ${roti()} roți"
}

class Taxi(val numar: String) : Masina() {
    override fun descriere() = "taxi $numar, ${super.descriere()}"
    // override fun roti() = 3   // eroare: 'roti' in 'Masina' is final and cannot be overridden
}

fun main() {
    println(Vehicul().descriere())
    println(Masina().descriere())
    println(Taxi("IS-07-TAX").descriere())
}

Rezultat

vehicul cu 0 roți
mașină cu 4 roți
taxi IS-07-TAX, mașină cu 4 roți

Greșeli frecvente

  • Moștenirea unei clase fără open: eroare this type is final, so it cannot be extended.
  • Suprascrierea unei metode fără open în clasa de bază: eroare 'x' in 'Baza' is final and cannot be overridden.
  • Apelarea unei metode open din constructorul clasei de bază: subclasa încă nu și-a inițializat proprietățile, deci metoda suprascrisă poate vedea null sau 0.

Comenzi înrudite

  • override și super: override înlocuiește o metodă sau o proprietate moștenită; super apelează versiunea din clasa de bază sau din interfață.
  • abstract: abstract marchează o clasă care nu poate fi creată direct și membri fără implementare, pe care subclasele trebuie să-i scrie.
  • class: class declară o clasă: un tip nou care grupează date (proprietăți) și comportament (metode).
  • interface: interface descrie ce poate face un obiect (metode și proprietăți), fără stare; o clasă poate implementa mai multe interfețe.
  • sealed class și sealed interface: sealed declară o ierarhie închisă: toate subtipurile sunt cunoscute la compilare, deci when le poate verifica pe toate fără else.