为什么 kotlin 中的属性覆盖使主构造函数属性为零

Vik*_*rya 3 java inheritance constructor overriding kotlin

我正在尝试将值传递给构造函数并打印值。

open class Car(c: Int){
    open var cost: Int = c
    init {
        println("This comes First $cost")
    }
}

open class Vehicle(cc: Int) : Car(cc) {
    override var cost: Int = 20000
    init {
        println("This comes Second $cost")
    }

    fun show(){
        println("cost = $cost")
    }
}

fun main() {
    var vehicle = Vehicle(1000)
    vehicle.show()
}
Run Code Online (Sandbox Code Playgroud)

输出

This comes First 0
This comes Second 20000
cost = 20000
Run Code Online (Sandbox Code Playgroud)

如果我只是评论这一行 override var cost: Int = 20000

输出将是

This comes First 1000
This comes Second 1000
cost = 1000
Run Code Online (Sandbox Code Playgroud)
  • 为什么在覆盖子类中的属性时超级构造函数成本为零?
  • 我需要将其与 java 概念进行比较,以便在此处更好地解释

Die*_*rin 7

在 Java 中创建可变属性 cost,你需要定义一个字段cost 和一个 getter 和 setter:

public class Car {

    private int cost;

    public Car(int c) {
        this.cost = c;
        System.out.println("This comes First " + getCost());
    }

    public int getCost() { return cost; }

    public void setCost(int cost) { this.cost = cost; }
}
Run Code Online (Sandbox Code Playgroud)

Kotlin在语言中嵌入了属性的概念,因此您只需像创建var 属性一样实现相同的效果:

open class Car(c : Int){
    open var cost : Int = c
    init {
        println("This comes First $cost")
    }
}
Run Code Online (Sandbox Code Playgroud)

从开发人员的角度来看,这要简洁得多,但实现是相同的。Kotlin 编译器在幕后为我们生成字段成本、get 方法set 方法。现在是有趣的部分。当您在父类中打开成本属性并在子类中覆盖它时,您实际上是在覆盖 get 方法。无论是在 Kotlin 还是 Java 中,都无法覆盖字段。

正如@Pawel 在他的回答中提到的,这是 Vehicle 子类的 Java 代码:

public class Vehicle extends Car {
   private int cost = 20000;

   @Override
   public int getCost() {
      return this.cost;
   }

   @Override
   public void setCost(int var1) {
      this.cost = var1;
   }

   public final void show() {
      System.out.println("cost = " + getCost());
   }

   public Vehicle(int cc) {
      super(cc);
      System.out.println("This comes Second " + getCost());
   }
}
Run Code Online (Sandbox Code Playgroud)

当您println("This comes First $cost")在父类中执行时,您实际上是在执行System.out.println("This comes First " + getCost());并且实际getCost()被调用的是子类Vehicle中的那个。由于子类成本字段尚未初始化,由于我们仍在执行super(cc)调用,因此其值为零。


Paw*_*wel 6

您是否查看了生成的字节码并尝试将其反向反编译回 java?如果您经常对 Kotlin 在幕后的工作方式感到困惑,它可以帮助您理解。

在这种情况下,您的 Java 类将如下所示(我反编译了您的代码并对其进行了一些清理):

public class Car {
   private int cost;

   public int getCost() {
      return this.cost;
   }

   public void setCost(int var1) {
      this.cost = var1;
   }

   public Car(int c) {
      this.cost = c;
      System.out.println("This comes First " + getCost());
   }
}

public class Vehicle extends Car {
   private int cost = 20000;

   public int getCost() {
      return this.cost;
   }

   public void setCost(int var1) {
      this.cost = var1;
   }

   public final void show() {
      System.out.println("cost = " + getCost());
   }

   public Vehicle(int cc) {
      super(cc);
      System.out.println("This comes Second " + getCost());
   }
}
Run Code Online (Sandbox Code Playgroud)

发生的open var只是Vehicle覆盖的setter 和 getter 的声明。

记住超类的初始化总是发生在子类之前,所以当Car构造函数被执行时Vehicle仍然没有初始化(Vehicle.cost仍然是0)。

这意味着在第一种情况下:

This comes First 0   // Car constructor prints 0 because it returns Vehicle.cost which is unitialized
This comes Second 20000  // Vehicle constructor returns initialized value
cost = 20000
Run Code Online (Sandbox Code Playgroud)

而在第二种情况下,你没有覆盖成本,两者CarVehicle返回Car.cost

This comes First 1000  // Car constructor assigns constructor parameter to Car.cost and returns it
This comes Second 1000  // Vehicle constructor returns Car.cost as well
cost = 1000
Run Code Online (Sandbox Code Playgroud)

另请注意,在第一种情况下,您的构造函数参数毫无意义:它被分配给Car.cost但该字段不可访问,因为它被Vehicle.cost.


Ten*_*r04 5

这种行为可能不直观,但它是属性如何与 JVM 一起工作的结果。

当您子类化 Car 时,Car 初始化发生在子类 Vehicle 初始化之前。所以printlnCarinit块中的调用会在Vehicle 初始化之前访问该属性。由于此访问相当于调用 Java 中的 getter 方法,因此它访问的是子类的 getter,而不是它自己的 getter,因为它已被覆盖。由于子类在此阶段尚未初始化,因此其 getter 返回支持字段的默认值 0。

如果您使用非原始的、不可为空的属性执行此操作,您可以“欺骗”Kotlin 为您提供一个 Java NullPointerException 异常,通常可以假定为非空。

在任何一种情况下,默认代码检查都会警告您在初始化期间尝试访问打开的属性,因为这种意外的行为。

解决方法是使用私有支持属性:

open class Car(c : Int){
    private var _cost: Int = c
    open var cost : Int
        get() = _cost
        set(value) { _cost = value }

    init {
        println("This comes First $_cost")
    }
}
Run Code Online (Sandbox Code Playgroud)