为什么在 Kotlin 中进行属性重写会使主构造函数属性变为零。

huangapple go评论80阅读模式
英文:

why property overriding in kotlin makes primary constructor property zero

问题

为什么在子类中覆盖属性时,超类构造函数的 cost 为零?
我需要将这与 Java 的概念进行比较,以便更好地解释。
英文:

I'm trying to pass value to constructor and print the values.

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

Output

This comes First 0
This comes Second 20000
cost = 20000

if i just comment this line
override var cost: Int = 20000

output would be

This comes First 1000
This comes Second 1000
cost = 1000
  • Why super constructor cost is zero when override the property in subclass?
  • I need this to be compared to java concept for better explanation here

答案1

得分: 9

在Java中,要创建一个可变属性 cost,您需要定义一个 field 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; }
}

Kotlin中有嵌入在语言中的 property 概念,因此您只需要创建一个 var 属性即可实现相同的效果:

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

从开发者的角度来看,这更加简洁,但实现原理是相同的。Kotlin编译器在后台为我们生成了一个 field cost,一个 get 方法 和一个 set 方法

现在是有趣的部分。当您将父类中的 cost 属性设置为 open,并在子类中进行重写时,实际上是在重写 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());
   }
}

当在父类中执行 println("This comes First $cost") 时,实际上是执行 System.out.println("This comes First " + getCost());,而实际调用的 getCost() 是子类 Vehicle 中的方法。由于子类的 cost 字段尚未初始化,因为我们仍然在执行 super(cc) 调用,所以它的值为零。

英文:

In Java to create a mutable property cost, you need to define a field cost and a getter and 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; }
}

Kotlin has the concept of a property embedded in the language, so you can achieve the same with only creating a var property as you did:

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

This is much more concise from the developer perspective, but the implementation is the same. Kotlin compiler is generating a field cost, a get method and set method for us under the hood.
Now the interesting part. When you make the cost property in the parent class open and overrides it in the child class, you are actually overriding the get method. It is not possible to override a field, neither in Kotlin nor Java.

As @Pawel mentioned in his answer that's the java code for the Vehicle child class:

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

When you execute println("This comes First $cost") in the parent class, you are actually executing System.out.println("This comes First " + getCost()); and the actual getCost() being called is the one in the child class Vehicle. As the child class cost field has not been initialized yet, as we are still executing the super(cc) call, its value is zero.

答案2

得分: 8

你是否查看过生成的字节码,并尝试将其反向反编译回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());
   }
}

正在发生的情况是,open var 仅是 setter 和 getter 的声明,Vehicle 对其进行了重写。

请记住,父类的初始化始终在子类之前发生,因此当执行 Car 构造函数时,Vehicle 尚未初始化(Vehicle.cost 仍为 0)。

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

This comes First 0   // Car 构造函数打印 0,因为它返回未初始化的 Vehicle.cost
This comes Second 20000  // Vehicle 构造函数返回初始化值
cost = 20000

而在第二种情况下,你不重写 costCarVehicle 都返回 Car.cost

This comes First 1000  // Car 构造函数将构造函数参数分配给 Car.cost 并返回它
This comes Second 1000  // Vehicle 构造函数也返回 Car.cost
cost = 1000

还要注意,在第一种情况下,你的构造函数参数是无意义的:它被分配给了 Car.cost,但该字段是不可访问的,因为它被 Vehicle.cost 遮蔽了。

英文:

Have you looked at generated bytecode and tried to reverse decompile it back to java? If you're confused how Kotlin works under the hood often times it can help you understand.

In this case your classes in Java would look like this (i decompiled your code and cleaned it up a little):

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

What's happening is open var is just declaration for setter and getter which Vehicle overrides.

Remember that initialization of super class always happen before child, so when Car constructor is executed Vehicle is still not initialized (Vehicle.cost is still 0).

That means in first case:

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

And in second case where you DON'T override the cost, both Car and Vehicle return 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

Also note that in first case your constructor parameter is meaningless: it gets assigned to Car.cost but that field is inaccessible because it's shadowed by Vehicle.cost.

答案3

得分: 5

这种行为可能不直观,但这是属性与JVM交互的结果。

当您对Car进行子类化时,Car的初始化会在子类Vehicle的初始化之前发生。因此,Car的init块中的println调用会在Vehicle初始化之前访问该属性。由于此访问相当于在Java中调用getter方法,它访问的是子类的getter,而不是它自己的getter,因为它已被重写。由于在这个阶段子类尚未初始化,它的getter返回支持字段的默认值0。

如果您对非原始的、非可为空的属性这样做,您可以“欺骗”Kotlin,使其在通常被认为是非空的情况下为您提供Java空指针异常。

无论哪种情况,由于这种意外的行为,默认的代码检查应该会警告您在初始化期间尝试访问开放属性。

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

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")
    }
}
英文:

This behavior may not be intuitive, but it's the result of how properties work with the JVM.

When you subclass Car, the Car initialization occurs before the subclass Vehicle initialization. So the println call in Car's init block accesses the property before Vehicle is initialized. Since this access amounts to calling a getter method in Java, it is accessing the subclass's getter, not its own since it's been overridden. Since the subclass has not initialized yet at this stage, its getter returns the default value of the backing field, 0.

If you do this with a non-primitive, non-nullable property, you can "trick" Kotlin into giving you a Java NullPointerException for what can normally be assumed to be non-null.

In either case, the default code inspections should warn you about trying to access an open property during initialization, because of this unexpected kind of behavior.

The work-around would be to use a private backing property:

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

答案4

得分: 1

用简单的话来说。

当创建对象 Vehicle(1000) 时,会发生以下步骤:

  • 首先调用初始化函数(init)
  • 然后初始化变量

步骤 1:它将尝试调用 Vehicle 的构造函数
步骤 2:由于它继承了 Car,它将首先尝试调用 Car 的构造函数
步骤 3:它总是寻找重写(override)的方法,而不是未重写的方法或属性,所以 $cost 指向了重写的属性。
步骤 4:当你打印 println("This comes First $cost") 时,override var cost : Int = 20000 尚未初始化,因为初始化函数(init)会在属性之前运行。
步骤 5:所以,默认情况下,$cost 是零。

只需尝试在 open var cost : Int = c 下面添加 var costTest : Int = c,并删除 "no open keyword"。
然后,在 Vehicle 的初始化函数(init)中添加以下代码:

println("This comes Second $cost $costTest")

这里,你将会得到 costTest = 1000

这是因为你没有重写 costTest

英文:

In simple words.

when object is created Vehicle(1000)

  • first init will get called
  • then variables get initialized

step 1:- it will try to call constructor of Vehicle
step 2:- since it inherited Car it will try to call constructor of Car first
step 3:- it always look for override methods not overridden methods or properties So, $cost is pointing to override property.
step 4:- override var cost : Int = 20000 is not initialized when you are printing
println("This comes First $cost") as init run first then property will initialized.
step 5:- So $cost is by default zero.

Just try this, below open var cost : Int = c
put, var costTest : Int = c. "no open keyword"
Then, on Vehicle init put,

println("This comes Second $cost $costTest")

here you will get costTest = 1000.

Its because costTest you haven't override

huangapple
  • 本文由 发表于 2020年4月9日 20:13:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/61120904.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定