почему переопределение свойства в kotlin делает свойство первичного конструктора нулевым - PullRequest
3 голосов
/ 09 апреля 2020

Я пытаюсь передать значение конструктору и вывести значения.

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

Вывод

This comes First 0
This comes Second 20000
cost = 20000

если я просто прокомментирую эту строку override var cost: Int = 20000

вывод будет

This comes First 1000
This comes Second 1000
cost = 1000
  • Почему стоимость супер-конструктора равна нулю при переопределении свойства в подклассе?
  • Мне нужно сравнить это с концепцией java для лучшего объяснения здесь

Ответы [ 4 ]

6 голосов
/ 09 апреля 2020

В Java, чтобы создать изменяемое свойство cost, вам необходимо определить поле стоимость и метод получения и установки:

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 имеет понятие свойства , встроенного в язык, поэтому вы можете достичь того же самого, только создав var свойство , как вы это сделали:

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

Это гораздо более лаконично с точки зрения разработчика, но реализация такая же. Kotlin компилятор генерирует поле стоимость, метод получения и метод установки для нас под капотом. Теперь интересная часть. Когда вы открываете свойство cost в родительском классе и переопределяете его в дочернем классе, вы фактически переопределяете метод get. Невозможно переопределить поле ни в Kotlin, ни в Java.

. Как отметил в своем ответе @Pawel, это код java для дочернего класса Vehicle:

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 . Поскольку поле стоимости дочернего класса еще не было инициализировано, так как мы все еще выполняем вызов super(cc), его значение равно нулю.

6 голосов
/ 09 апреля 2020

Вы смотрели на сгенерированный байт-код и пытались отменить декомпиляцию обратно до java? Если вы не понимаете, как часто работает Kotlin, это может помочь вам понять.

В этом случае ваши классы в Java будут выглядеть следующим образом (я декомпилировал ваш код и очистил его 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());
   }
}

То, что происходит, open var - это просто объявление для установщика и получателя, которое Vehicle переопределяет.

Помните, что инициализация суперкласса всегда происходит перед потомком, поэтому, когда 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

И во втором случае, когда вы не делаете 'T переопределяют стоимость, и Car, и Vehicle возвращают 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

Также обратите внимание, что в первом случае ваш параметр конструктора не имеет смысла: ему присваивается значение Car.cost, но это поле недоступен, потому что затенен Vehicle.cost.

5 голосов
/ 09 апреля 2020

Это поведение может быть не интуитивно понятным, но это результат того, как свойства работают с JVM.

Когда вы создаете подкласс Car, инициализация Car происходит до инициализации подкласса Vehicle. Таким образом, вызов println в блоке Car init обращается к свойству до инициализации Vehicle. Поскольку этот доступ сводится к вызову метода получения в Java, он обращается к получателю подкласса, а не к своему собственному, так как он был переопределен. Поскольку на этом этапе подкласс еще не инициализирован, его метод получения возвращает значение по умолчанию для поля поддержки, 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")
    }
}
1 голос
/ 09 апреля 2020

Простыми словами.

при создании объекта Vehicle(1000)
- будет вызван первый init - затем инициализируются переменные

шаг 1: - он попытается вызвать конструктор из Vehicle
шаг 2: - поскольку он унаследовал Car, он попытается вызвать конструктор Car first
, шаг 3: - он всегда ищет переопределенные методы, а не переопределенные методы или свойства. Итак, $ cost указывает на свойство переопределения.
шаг 4: - override var cost : Int = 20000 не инициализируется при печати println("This comes First $cost") при первом запуске init, затем свойство будет инициализировано.
шаг 5: - Таким образом, $ cost по умолчанию равен нулю.

Просто попробуйте это ниже open var cost : Int = c
put, var costTest : Int = c. «нет открытого ключевого слова»
Затем, при установке транспортного средства,

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

здесь вы получите costTest = 1000.

Это потому, что costTest у вас не переопределен

...