Ошибка или функция: Kotlin позволяет изменить "val" на "var" в наследовании
Я только начал изучать язык Котлин. Я борюсь с наследованием, var & val и побочными эффектами.
Если я объявляю признак A
с val x
и переопределяю x
в AImpl
, его можно переопределить как var
(см. код ниже). Удивительно, что метод print()
в A
зависит от переназначения x
, хотя x
является значением в A
. Это ошибка или функция?
код:
trait A {
fun print() {
println("A.x = $x")
}
val x : Int;
}
class AImpl(x : Int) : A {
override var x = x; // seems like x can be overriden as `var`
}
fun main(args: Array<String>) {
val a = AImpl(2)
a.print() // A.x = 2
a.x = 3; // x can be changed
// even though print() is defined in trait A
// where x is val it prints x = 3
a.print() // A.x = 3
}
Мне известно, что если я определяю A
с типом A
явно, то нельзя изменить x
:
val a = AImpl(2) : A
a.x = 3 // ERROR: value x cannot be reassigned
Но, как показывает первый случай, наследование может вызвать побочные эффекты, которые явно не предназначены для A
. Как защитить значения от изменения путем наследования?
Ответы
Ответ 1
Вы можете сделать свой val
final
, т.е. запретить переопределять его вообще.
Если вы определяете val
в классе, по умолчанию это final
.
Кроме того, если вам нужно переопределить val
с помощью var
, но не хотите, чтобы сеттер был общедоступным, вы можете так сказать:
override var x = 1
private set
Переопределение val
с помощью var
- это функция. Это эквивалентно добавлению метода set, в то время как в суперклассе существовал только метод get. И это довольно важно для реализации некоторых шаблонов, таких как интерфейсы только для чтения.
Нет способа "защитить" ваш val
от переопределения таким образом, который позволяет изменять мутацию, отличную от ее final
, потому что val
не означает "неизменяемую ссылку", а просто "только для чтения" имущество". Другими словами, когда ваш признак A
объявляет val
, это означает, что через ссылку типа A
клиент не может записать этот val
, никаких других гарантий не предусмотрено, или действительно возможно.
P.S. Точки с запятой являются необязательными в Котлине, не стесняйтесь полностью их опустить
Ответ 2
Я бы рассмотрел эту функцию, так как изменение val в var накладывает более низкие ограничения на использование, а не может сломать код суперкласса. Аналогичную ситуацию можно наблюдать с помощью модификаторов видимости:
trait A {
protected fun print() {
...
}
}
class AImpl: A {
public override fun print() {
...
}
}
В этом примере ограничения видимости также смягчаются подклассом, хотя некоторые люди рассматривают этот метод как антипаттерн.
Как защитить значения от изменения путем наследования?
В kotlin вы можете явно определить, может ли какой-либо конкретный член класса быть переопределен подклассом с помощью модификатора open
. Однако в чертах все участники открыты по умолчанию. Решение состоит в том, чтобы заменить класс классом, чтобы вы могли контролировать наследование:
abstract class A {
fun print() {
...
}
val x : Int = 2;
}
class AImpl(x : Int) : A() {
override var x = x // compilation error
}