Ошибка или функция: 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
}