Я хочу знать, почему именно мой код возвращает "nullnullnull"
вместо ожидаемого ответа и почему он работает правильно при использовании метода.
Рассмотрим следующий код:
open class Base(
open val a: String,
open val b: String,
open val c: String,
) {
val concatenation = a + b + c
}
class Derived(
override val a: String,
override val b: String,
override val c: String
) : Base(a = a, b = b, c = c)
fun main() {
val derived = Derived(a = "foo", b = "bar", c = "baz")
println(derived.concatenation)
}
В этом примере выводится "nullnullnull"
вместо "foobarbaz"
.
Хотя, если вы замените val concatenation = a + b + c
на fun concatenation() = a + b + c
в суперклассе, кажется, что этот вызов метода работает нормально.
Кроме того, IDEA предупреждает о Accessing non-final property <property> in constructor
при использовании val concatenation = a + b + c
, но я не уверен, что именно это означает.
Это что-то с порядком инициализации свойств базового и производного классов? Я подумал, что, возможно, я использую concatenation
из базового класса, но при наследовании я также вызываю конструктор базового класса, и должны быть инициализированы те же свойства с теми же строками.
@Alexander Хотя прямой причиной обоих является то, что базовый класс вызывает что-то, что переопределяет производный класс, в этом вопросе это не так очевидно, IMO.
@Александр, большая разница, которую я вижу, заключается в том, что мой код не выдает NPE (даже если все свойства не допускают значения NULL), а также я не переопределяю concatenation
в производном классе. Насколько я понимаю, созданные базовый объект и производный объект должны иметь все данные, касающиеся свойств a, b и c, поскольку они напрямую передаются обоим конструкторам.
Поскольку вы переопределили a, b, c; с какой стати ваш базовый класс должен знать значение переопределенных полей?
Я предполагаю, что вы хотели бы параметризировать a, b, c вместо того, чтобы переопределять их.
open class Base(
val a: String,
val b: String,
val c: String,
) {
val concatenation = a + b + c
}
class Derived(
a: String,
b: String,
c: String
) : Base(a = a, b = b, c = c)
fun main() {
val derived = Derived(a = "foo", b = "bar", c = "baz")
println(derived.concatenation)
}
Это тоже хорошее решение, спасибо
По моему мнению, Accessing non-final property <property> in constructor
следовало спроектировать как ошибку компиляции, а не просто как предупреждение, поскольку это приводит к странному поведению, которое даже не обязательно определено в документации. Например, вы можете легко создать исключение NullPointerException без использования !!
или любого межоперационного кода Java, сделав это.
«Неокончательный» означает то же самое, что и «открытый». Вы вызываете открытые свойства или функции из конструктора, что может создать большие проблемы и никогда не следует этого делать.
Код, который присваивает начальные значения свойств, считается частью конструктора, поэтому ваш код = a + b + c
является частью вашего конструктора и вызывает предупреждение.
Вот что происходит, чтобы получить нули. Конструктор производного класса передает три значения конструктору суперкласса. Суперкласс присваивает эти значения вспомогательным полям суперкласса своих свойств a
, b
и c
. Затем concatenation
вызывает свойства a
, b
и c
, чтобы получить их значения, но поскольку мы на самом деле являемся экземпляром Derived, эти свойства были переопределены и указывают на другие поля поддержки, чем те, которые были инициализированы как часть супер сорт.
Кроме того, поскольку мы все еще выполняем конструктор суперкласса, вспомогательные поля производного класса еще не инициализированы, поэтому они все еще содержат нулевые значения.
Интересный,. Спасибо за ответ, я не ожидал, что свойства будут иметь значение null перед инициализацией, я думал, что Kotlin защищает от дурака каждый аспект свойственной Java возможности обнуления.
Отвечает ли это на ваш вопрос? Котлин вызывает нефинальную функцию в конструкторе