初学者问题-继承-为什么不使用我的年龄构造函数参数?

时间:2018-11-06 20:52:42

标签: kotlin

在这里出现Noob问题,我正在通过Udemy初学者Kotlin课程学习,我无法弄清楚为什么在使用派生类时不使用我的age参数,但是在我的基础学习时可以使用类。

人员班

open class Person(open var firstname: String, open var surname: String,
          open var age: Int) {

val thisyear: Int = Calendar.getInstance().get(Calendar.YEAR)

val dob = thisyear - age

fun printperson() {

    println("$firstname $surname was born in $dob")

}
}

学生班

class Student(override var firstname: String, override var surname: 
                String, override var age: Int, val studentID: Int):    
                    Person(firstname, surname, age) {

fun returnDetails() {

    println("Hello $firstname, your Student ID is: $studentID")
}
}

主要

fun main(args: Array<String>) {

val studentMike = Student(firstname = "Mike", 
                  surname = "Stand", age = 67, studentID = 8899)

studentMike.printperson()
studentMike.returnDetails()

val personBill = Person(firstname = "Bill", surname = "Hook", age = 34)
personBill.printperson()

}

输出

Mike Stand was born in 2018
Hello Mike, your Student ID is: 8899
Bill Hook was born in 1984

如您所见,Bill是Person类中方法的直接使用,而Mike是间接调用,年龄参数应该是通过Student类从Person类继承的。

看看Kotlin的官方文档,问题似乎与“ Derived class initialisation order”有关,但对于我一生来说,我不太想知道如何解决此问题。

感谢您的帮助,

对不正确的术语表示歉意。总是为能做得更好而感到高兴。

2 个答案:

答案 0 :(得分:2)

您要引用的文档明确表示“在设计基类时,因此应避免在构造函数,属性初始化程序和init块中使用开放成员”

这正是您的示例中发生的情况。在基类Person中,您可以使用构造函数中的开放成员年龄(计算dob的行)。 应该避免这种情况,因为执行该行计算dob时,age尚未从派生类接收到值。

好的,我没有回答“如何更正此问题?”但我希望它有助于弄清发生了什么事

答案 1 :(得分:1)

Umberto Cozzi所说的是正确的,这与您在构造函数中引用开放值有关。如果逐步执行代码,您将看到事件的顺序为:

  1. 您调用Student构造函数,并传递age的值。
  2. 构造函数要做的第一件事(在构造Student对象之前)是超类的构造函数。
  3. 在构造超类(Person)时,您遇到了以下代码行:val dob = thisyear - ageage是开放成员(即在子类Student中被覆盖)。因此,应从Student中检索该值。问题在于,此时Student尚未被完全构造(记住其构造函数所做的第一件事就是调用超类的构造函数)。因此,Person尚不能问Student应该是什么值age,因为尚未设置该值。如果您单步执行代码并在此代码行中检出age的值,则该值为零(Int的默认值)。

那么问题是怎么办?我认为您应该问的是:为什么Student会覆盖age(实际上是firstnamesurname)。 agefirstname之间surnamePersonStudent的行为是否有不同?答案可能是否定的。因此Student不应覆盖这些属性:而是应将它们简单地声明为构造函数参数(没有valvar)并将这些值传递给基类。换句话说,Student应该如下所示:

class Student(firstname: String, surname: String, age: Int, val studentID: Int) :
        Person(firstname, surname, age) {
    ...

您可能还想知道,声明thisyear的代码行实际上创建了Person的属性,称为thisyear,我想您实际上并没有想。直接在类中(而不是在函数中)声明的任何valvar成员都是属性的声明(这就是在构建{时立即计算所有这些属性的原因。 {1}}对象)。因此,您可能希望将其内联为:

Person

如果计算更为复杂并且需要更多代码行,则只需创建一个私有方法(例如val dob = Calendar.getInstance().get(Calendar.YEAR) - age )并调用该方法,例如calculateDob

还有一点异常,val dob = calculateDob(age)age(即可以更改),而vardob(即不能更改)。因此,用户可以更改val的值,但是age不会被更新。解决此问题的一种可能方法是更改​​dob,以使其不是构造过程中为其分配(只读)值的属性,而应使其成为具有getter的属性,该属性将在每次构造时均计算该值称为,例如

dob

另一种选择是为val dob get() = Calendar.getInstance().get(Calendar.YEAR) - age 添加一个支持字段和getter / setter,并在age更新时更新dob