Я просматривал эффективные слайды по scala, и на слайде 10 упоминается, что никогда не следует использовать val
в trait
для абстрактных членов и вместо этого использовать def
. На слайде нет подробного описания того, почему использование абстрактных val
в trait
является антипаттерном. Я был бы признателен, если бы кто-нибудь смог объяснить лучшую практику использования val против def в трейтах для абстрактных методов
Определение def
может быть реализовано одним из следующих элементов: def
, val
, lazy val
или object
. Таким образом, это наиболее абстрактная форма определения члена. Поскольку трейты обычно являются абстрактными интерфейсами, то, говоря, что вам нужен val
, вы говорите, как должна работать реализация. Если вы просите val
, реализующий класс не может использовать def
.
Значение val
необходимо только в том случае, если вам нужен стабильный идентификатор, например, для типа, зависящего от пути. Обычно в этом нет необходимости.
Сравните:
trait Foo { def bar: Int }
object F1 extends Foo { def bar = util.Random.nextInt(33) } // ok
class F2(val bar: Int) extends Foo // ok
object F3 extends Foo {
lazy val bar = { // ok
Thread.sleep(5000) // really heavy number crunching
42
}
}
Если бы у вас был
trait Foo { val bar: Int }
вы бы не смогли определить F1
или F3
.
Хорошо, и чтобы запутать вас и ответить @om-nom-nom - использование абстрактных val
может вызвать проблемы с инициализацией:
trait Foo {
val bar: Int
val schoko = bar + bar
}
object Fail extends Foo {
val bar = 33
}
Fail.schoko // zero!!
Это уродливая проблема, которая, по моему личному мнению, должна исчезнуть в будущих версиях Scala путем исправления в компиляторе, но да, в настоящее время это также является причиной, по которой не следует использовать абстрактные val
.
Edit (Jan 2016): Разрешено переопределять объявление абстрактного val
с помощью реализации lazy val
, так что это также предотвратит сбой инициализации.
Я предпочитаю не использовать val
в трейтах, поскольку объявление val имеет неясный и неинтуитивный порядок инициализации. Вы можете добавить трейты в уже работающую иерархию, и это сломает все, что работало до этого, см. мою тему: https://stackoverflow.com/questions/12091689/why-using-plain-val-in-non-final-classes.
Следует помнить обо всем, что касается использования этого объявления val, которое в конечном итоге приведет вас к ошибке.
Но бывают случаи, когда без использования val
не обойтись. Как уже говорил @0__, иногда нужен стабильный идентификатор, а def
таковым не является.
Я бы привел пример, чтобы показать, о чем он говорит:
trait Holder {
type Inner
val init : Inner
}
class Access(val holder : Holder) {
val access : holder.Inner =
holder.init
}
trait Access2 {
def holder : Holder
def access : holder.Inner =
holder.init
}
Этот код выдает ошибку:
StableIdentifier.scala:14: error: stable identifier required, but Access2.this.holder found.
def access : holder.Inner =
Если на минуту задуматься, то можно понять, что у компилятора есть повод для недовольства. В случае Access2.access
он никак не мог вывести возвращаемый тип. А def holder
означает, что он может быть реализован широким образом. Он мог бы возвращать различные держатели для каждого вызова, и эти держатели могли бы включать различные внутренние
типы. Но виртуальная машина Java ожидает возврата одного и того же типа.
Постоянное использование def кажется немного неудобным, так как ничего подобного не получится:
trait Entity { def id:Int}
object Table {
def create(e:Entity) = {e.id = 1 }
}
Вы получите следующую ошибку:
error: value id_= is not a member of Entity