Ошибка или фича: Kotlin позволяет менять val на var при наследовании

Я только начал изучать язык Kotlin. Я борюсь с наследованием, 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. Как защитить значения от изменения по наследству?


person miho    schedule 02.04.2014    source источник


Ответы (2)


Вы можете сделать свой 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. Точки с запятой необязательны в Kotlin, не стесняйтесь их вообще опускать.

person Andrey Breslav    schedule 03.04.2014

Я бы рассматривал это как функцию, поскольку изменение val на var накладывает более слабые ограничения на использование и не может нарушить код суперкласса. Похожую ситуацию можно наблюдать и с модификаторами видимости:

trait A {
  protected fun print() {
    ...
  }
}

class AImpl: A {
  public override fun print() {
    ...
  }
}

В этом примере ограничения видимости также ослабляются подклассом, хотя некоторые считают этот метод антипаттерном.

Как защитить значения от изменения по наследству?

В kotlin вы можете явно указать, может ли какой-либо конкретный член класса быть переопределен подклассом, используя модификатор open. Однако в трейтах все члены по умолчанию открыты. Решение состоит в том, чтобы заменить trait классом, чтобы вы могли управлять наследованием:

abstract class A {
  fun print() {
    ...
  }

  val x : Int = 2;
}

class AImpl(x : Int) : A() {
  override var x = x // compilation error
}
person Jk1    schedule 03.04.2014