Например, если у меня есть
sealed trait Foo {
val value1 = "something"
val value2 = "else"
}
Могу ли я получить что-то вроде этого?
case class Bar(barValue = s"${super.value1} ${super.value2}") extends Foo
На данный момент у меня ошибка компилятора: значение barValue не является членом AnyRef
PS Кажется, это легко сделать, но я не понял, почему я получил эту ошибку компилятора Это интересный случай, потому что оба значения видны в конструкторе, но компилятор не работает
Не могу воспроизвести value barValue is not a member of AnyRef
. Я могу воспроизвести value value1 is not a member of AnyRef
, value value2 is not a member of AnyRef
https://scastie.scala-lang.org/DmytroMitin/BM6uIxGhTtmIUZoex820aA/1
Обходной путь — сделать класс case обычным классом и определить фабричные методы.
class Bar private extends Foo {
val barValue: String = s"${value1} ${value2}"
}
object Bar {
def apply(): Bar = new Bar
def apply(_barValue: String): Bar = new Bar {
override val barValue: String = _barValue
}
}
Bar().barValue // something else
Bar("abc").barValue // abc
Или это может быть кейс-класс
case class Bar() extends Foo {
val barValue: String = s"${value1} ${value2}"
}
object Bar {
def apply(_barValue: String): Bar = new Bar {
override val barValue: String = _barValue
}
}
Хотя особого смысла держать класс case-классом нет. equals
, hashcode
, toString
и т. д. будут генерироваться без учета barValue
.
В отличие от родительских def
, к родительским val
следует обращаться без super
.
Ошибка value1 is not a member of AnyRef
понятна. Значения по умолчанию параметров конструктора проверяются на тип во внешней области, а не в родительской области. И, вероятно, вы помещаете все в какой-то объект. super
относится к super
этого внешнего объекта, то есть AnyRef
. А у AnyRef
нет члена value1
. Если вы сделаете все на верхнем уровне, ошибка изменится на this can be used only in a class, object, or template
.
Я бы сказал нет, потому что case class Bar
создает не только Bar
конструктор (new Bar
), но и фабричный метод в сопутствующем объекте Bar.apply
), а последний не может получить доступ к Foo
конструктору, даже если Bar
может.
И Bar
может получить доступ к super
в теле, но не обязательно в значении по умолчанию, поскольку значение по умолчанию... внизу также является методом в компаньоне.
Bar() // is seen by JVM as more or less as
Bar.apply(Bar.$init$1) // creates the default value and pass it to apply
И даже если этот метод может использовать что-то, переданное извне (например, copy
использует существующие значения из скопированного объекта), здесь вы:
Foo
, потом Bar
)поэтому значения по умолчанию создаются задолго до инициализации val value1
и val value2
. Вам придется хранить их в другом месте, чтобы сделать их доступными.
это более интересно, потому что мы получаем ту же ошибку, даже если Bar не является классом case, а обычным классом, и он не предоставляет для него никакого фабричного метода применения, но ошибка та же самая
Это вторая часть ответа: значения по умолчанию оцениваются перед передачей в метод, а значения super
инициализируются внутри конструктора. Таким образом, у методов по умолчанию даже не будет ничего для вызова, поскольку ссылка не была возвращена конструктором, а внутри конструктора — единственное место, где уже есть ссылка на this
(при этом все val
инициализируются только что).
Нет, вы не можете использовать super
в значениях по умолчанию для полей класса case. Однако вы можете добиться желаемого эффекта, используя следующий подход:
sealed trait Foo {
val value1 = "something"
val value2 = "else"
def concatValues = s"$value1 $value2"
}
case class Bar(_barValue: String = "") extends Foo {
val barValue = if (_barValue.isEmpty) concatValues else _barValue
}
equals
, hashcode
, toString
и т. д. будут генерироваться относительно _barValue
, а не barValue
.
«Значения параметров конструктора по умолчанию проверяются на тип во внешней области, а не в родительской области» - это было ключом к этой проблеме. Я решил это так же - с помощью настраиваемого фабричного метода в наследовании компаньона и компаньона вместо наследования класса case