Inisialisasi variabel Kotlin untuk kelas anak berperilaku aneh untuk menginisialisasi variabel dengan nilai 0

16

Saya telah membuat hierarki kelas berikut:

open class A {
    init {
        f()
    }

    open fun f() {
        println("In A f")
    }
}

class B : A() {
    var x: Int = 33

    init {
        println("x: " + x)
    }

    override fun f() {
        x = 1
        println("x in f: "+ x)
    }

    init {
        println("x2: " + x)
    }
}

fun main() {
    println("Hello World!!")
    val b = B()
    println("in main x : " + b.x)
}

Output dari kode ini adalah

Hello World!!
x in f: 1
x: 33
x2: 33
in main x : 33

Tetapi jika saya mengubah inisialisasi xdari

var x: Int = 33

untuk

var x: Int = 0

output menunjukkan permohonan metode berbeda dengan output di atas:

Hello World!!
x in f: 1
x: 1
x2: 1
in main x : 1

Adakah yang tahu mengapa inisialisasi dengan 0menyebabkan perilaku yang berbeda dari yang dengan nilai lain?

PRATYUSH SINGH
sumber
4
Tidak terkait langsung, tetapi memanggil metode yang dapat ditimpa dari konstruktor umumnya bukan praktik yang baik karena dapat menyebabkan perilaku yang tidak terduga (dan secara efektif melanggar kontrak / invarian superclass dari subclass).
Adam Hošek

Jawaban:

18

kelas super diinisialisasi sebelum sub kelas.

Panggilan konstruktor B memanggil konstruktor A, yang memanggil fungsi f mencetak "x dalam f: 1", setelah A diinisialisasi, sisa B diinisialisasi.

Jadi pada dasarnya, pengaturan nilai sedang ditimpa.

(Ketika Anda menginisialisasi primitif dengan nilai nol di Kotlin, secara teknis mereka tidak menginisialisasi sama sekali)

Anda dapat mengamati perilaku "menimpa" ini dengan mengubah tanda tangan dari

var x: Int = 0 untuk var x: Int? = 0

Karena xbukan lagi primitif int, bidang sebenarnya diinisialisasi ke nilai, menghasilkan output:

Hello World!!
x in f: 1
x: 0
x2: 0
in main x : 0
Sxtanna
sumber
5
Ketika Anda menginisialisasi primitif dengan nilai nol di Kotlin, mereka secara teknis tidak menginisialisasi sama sekali adalah apa yang ingin saya baca ... Terima kasih!
deHaar
Ini sepertinya masih bug / inkonsistensi.
Kroppeb
2
@ Kalpeb ini hanya Java, perilaku yang sama dapat diamati dalam kode Java saja. Itu tidak ada hubungannya dengan Kotlin
Sxtanna
8

Perilaku ini dijelaskan dalam dokumentasi - https://kotlinlang.org/docs/reference/classes.html#derived-class-initialization-order

Jika salah satu dari properti tersebut digunakan dalam logika inisialisasi kelas dasar (baik secara langsung atau tidak langsung, melalui implementasi anggota terbuka lain yang ditimpa), itu dapat menyebabkan perilaku yang salah atau kegagalan runtime. Saat mendesain kelas dasar, Anda harus menghindari penggunaan anggota terbuka di konstruktor, inisialisasi properti, dan blok init.

UPD:

Ada bug yang menghasilkan inkonsistensi ini - https://youtrack.jetbrains.com/issue/KT-15642

Ketika sebuah properti ditetapkan sebagai efek samping dari panggilan fungsi virtual di dalam super constructor, initializer-nya tidak menimpa properti jika ekspresi initializer adalah tipe nilai default (nol, nol primitif).

vanyochek
sumber
1
Selanjutnya, IntelliJ memperingatkan Anda tentang hal itu. Memanggil f()di initblok Amemberi peringatan "Memanggil fungsi non-final f dalam konstruktor"
Kroppeb
Dalam dokumentasi yang Anda berikan, dikatakan "inisialisasi kelas dasar dilakukan sebagai langkah pertama dan dengan demikian terjadi sebelum logika inisialisasi dari kelas turunan dijalankan" yang persis seperti yang terjadi pada contoh pertama dalam pertanyaan. Namun dalam contoh kedua instruksi inisialisasi ( var x: Int = 0) dari kelas turunan tidak berjalan sama sekali yang bertentangan dengan apa yang dikatakan dokumentasi yang membuat saya percaya bahwa ini mungkin bug.
Subaru Tashiro
@ SubaruTashiro Ya, Anda benar. Ini masalah lain - youtrack.jetbrains.com/issue/KT-15642 .
vanyochek