Я проходил эффективные слайды scala, и он упоминает на слайде 10, чтобы никогда не использовать val
в trait
для абстрактных членов и использовать def
. В слайде не упоминается подробно, почему использование абстрактного val
в trait
является анти-шаблоном. Я был бы признателен, если бы кто-то мог объяснить лучшую практику вокруг использования val vs def в признаке абстрактных методов.
Когда использовать val или def в Scala чертах?
Ответ 1
A def
может быть реализован либо с помощью def
, a val
, a lazy val
, либо object
. Так что это самая абстрактная форма определения члена. Поскольку черты обычно являются абстрактными интерфейсами, говоря, что вы хотите, чтобы val
говорил, как должна выполняться реализация. Если вы запрашиваете val
, класс реализации не может использовать def
.
A 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-using abstract val
может вызвать проблемы с инициализацией:
trait Foo {
val bar: Int
val schoko = bar + bar
}
object Fail extends Foo {
val bar = 33
}
Fail.schoko // zero!!
Это уродливая проблема, которая по моему мнению должна уйти в будущих версиях Scala, исправив ее в компиляторе, но да, в настоящее время это также является причиной, по которой нельзя использовать абстрактные val
s.
Изменить (январь 2016): вы можете переопределить абстрактное объявление val
с реализацией lazy val
, чтобы также предотвратить сбой инициализации.
Ответ 2
Я предпочитаю не использовать val
в чертах, потому что декларация val имеет непонятный и неинтуитивный порядок инициализации. Вы можете добавить черту к уже работающей иерархии, и это сломает все, что сработало до этого, см. Мою тему: зачем использовать простой val в нечетных классах
Вы должны держать все в стороне от использования этих деклараций 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
означает, что он может быть реализован в широком смысле. Он может возвращать разные держатели для каждого вызова, и что держатели будут включать разные типы Inner
. Но виртуальная машина Java ожидает, что тот же тип будет возвращен.
Ответ 3
Всегда использовать def кажется немного неудобным, поскольку что-то вроде этого не будет работать:
trait Entity { def id:Int}
object Table {
def create(e:Entity) = {e.id = 1 }
}
Вы получите следующую ошибку:
error: value id_= is not a member of Entity