英文:
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
而在第二种情况下,你不重写 cost
,Car
和 Vehicle
都返回 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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论