การกำหนดค่าเริ่มต้นตัวแปร Kotlin สำหรับคลาสลูกทำงานแปลกสำหรับการเริ่มต้นตัวแปรด้วยค่า 0


16

ฉันได้สร้างลำดับชั้นของคลาสต่อไปนี้:

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)
}

ผลลัพธ์ของรหัสนี้คือ

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

แต่ถ้าฉันเปลี่ยนการเริ่มต้นxจาก

var x: Int = 33

ถึง

var x: Int = 0

ผลลัพธ์แสดงให้เห็นถึงการภาวนาของวิธีการในทางตรงกันข้ามกับการส่งออกข้างต้น

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

ไม่มีใครรู้ว่าทำไมการเริ่มต้นด้วย0ทำให้เกิดพฤติกรรมที่แตกต่างกว่าคนที่มีค่าอื่นได้หรือไม่


4
ไม่เกี่ยวข้องโดยตรง แต่การเรียกวิธีการ overridable จากตัวสร้างมักไม่ใช่วิธีปฏิบัติที่ดีเนื่องจากอาจนำไปสู่พฤติกรรมที่ไม่คาดคิด (และการทำลาย superclass contract / invariants จากคลาสย่อยอย่างมีประสิทธิภาพ)
Adam Hošek

คำตอบ:


18

ระดับซุปเปอร์จะเริ่มต้นได้ก่อนชั้นย่อย

การเรียกคอนสตรัคเตอร์ของ B เรียกคอนสตรัคเตอร์ของ A ซึ่งเรียกใช้ฟังก์ชัน f พิมพ์ "x ใน f: 1" หลังจากเริ่มต้น A แล้วส่วนที่เหลือของ B จะเริ่มต้นได้

ดังนั้นการตั้งค่าจะถูกเขียนทับ

(เมื่อคุณเริ่มต้นดั้งเดิมด้วยค่าศูนย์ใน Kotlin พวกเขาในทางเทคนิคก็ไม่ได้เริ่มต้นเลย)

คุณสามารถสังเกตพฤติกรรม "overwrite" นี้ได้โดยเปลี่ยนลายเซ็นจาก

var x: Int = 0 ถึง var x: Int? = 0

เนื่องจากxไม่เป็นแบบดั้งเดิมอีกต่อไปintแล้วฟิลด์จะได้รับการเริ่มต้นเป็นค่าจริง ๆ แล้วสร้างเอาต์พุต:

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

5
เมื่อคุณเริ่มต้นดั้งเดิมด้วยค่าศูนย์ใน Kotlin พวกเขาในทางเทคนิคไม่ต้องเริ่มต้นเลยคือสิ่งที่ฉันต้องการอ่าน ... ขอบคุณ!
deHaar

สิ่งนี้ยังดูเหมือนว่ามีข้อบกพร่อง / ไม่สอดคล้องกัน
Kroppeb

2
@Kroppeb นี่เป็นเพียง Java, พฤติกรรมเดียวกันสามารถสังเกตได้ในรหัส Java เพียงอย่างเดียว ไม่มีส่วนเกี่ยวข้องกับ Kotlin
Sxtanna

8

พฤติกรรมนี้อธิบายไว้ในเอกสารประกอบ - https://kotlinlang.org/docs/reference/classes.html#derived-class-initialization-order

หากมีการใช้คุณสมบัติเหล่านี้ในตรรกะการกำหนดค่าเริ่มต้นคลาสพื้นฐาน (ไม่ว่าโดยตรงหรือโดยอ้อมผ่านการใช้งานสมาชิกแบบเปิดที่ถูกแทนที่) มันอาจนำไปสู่พฤติกรรมที่ไม่ถูกต้องหรือความล้มเหลวรันไทม์ เมื่อออกแบบคลาสพื้นฐานดังนั้นคุณควรหลีกเลี่ยงการใช้สมาชิกแบบเปิดในตัวสร้าง initializers คุณสมบัติและบล็อก init

UPD:

มีข้อผิดพลาดที่ทำให้เกิดความไม่สอดคล้องกันนี้ - https://youtrack.jetbrains.com/issue/KT-15642

เมื่อคุณสมบัติถูกกำหนดให้เป็นผลข้างเคียงของการเรียกฟังก์ชันเสมือนภายในตัวสร้าง super เครื่องมือเริ่มต้นจะไม่เขียนทับคุณสมบัติหากนิพจน์ initializer เป็นค่าเริ่มต้นของประเภท (null ศูนย์ดั้งเดิม)


1
นอกจากนี้ IntelliJ เตือนคุณเกี่ยวกับเรื่องนี้ โทรf()ในinitบล็อกของAให้คำเตือน "การเรียกฟังก์ชั่นที่ไม่ใช่ขั้นสุดท้าย f ในตัวสร้าง"
Kroppeb

ในเอกสารที่คุณให้ไว้จะมีข้อความระบุว่า"การกำหนดค่าเริ่มต้นคลาสพื้นฐานเป็นขั้นตอนแรกและจะเกิดขึ้นก่อนที่จะเริ่มต้นตรรกะของคลาสที่ได้รับมา"ซึ่งเป็นสิ่งที่เกิดขึ้นในตัวอย่างแรกของคำถาม อย่างไรก็ตามในตัวอย่างที่สองคำสั่งการเริ่มต้น ( var x: Int = 0) ของคลาสที่ได้รับนั้นไม่ได้ทำงานเลยซึ่งตรงกันข้ามกับสิ่งที่เอกสารบอกว่าทำให้ฉันเชื่อว่านี่อาจเป็นข้อผิดพลาด
Subaru Tashiro

@SubaruTashiro ใช่คุณพูดถูก มันเป็นปัญหาอื่น - youtrack.jetbrains.com/issue/KT-15642
vanyochek
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.