A def
может быть реализован с помощью a def
, a val
, a lazy val
или an object
. Так что это наиболее абстрактная форма определения члена. Поскольку черты обычно являются абстрактными интерфейсами, то, что вы хотите, val
означает, как должна работать реализация. Если вы запрашиваете a val
, реализующий класс не может использовать def
.
A val
нужен только в том случае, если вам нужен стабильный идентификатор, например, для типа, зависящего от пути. Это то, что вам обычно не нужно.
Сравните:
trait Foo { def bar: Int }
object F1 extends Foo { def bar = util.Random.nextInt(33) }
class F2(val bar: Int) extends Foo
object F3 extends Foo {
lazy val bar = {
Thread.sleep(5000)
42
}
}
Если у тебя есть
trait Foo { val bar: Int }
вы не сможете определить F1
или F3
.
Хорошо, и чтобы запутать вас, ответьте @ om-nom-nom - использование абстрактных val
s может вызвать проблемы с инициализацией:
trait Foo {
val bar: Int
val schoko = bar + bar
}
object Fail extends Foo {
val bar = 33
}
Fail.schoko
Это уродливая проблема, которая, по моему мнению, должна исчезнуть в будущих версиях Scala, исправив ее в компиляторе, но да, в настоящее время это также причина, по которой не следует использовать абстрактные val
s.
Изменить (январь 2016 г.): вам разрешено переопределить абстрактное val
объявление с помощью lazy val
реализации, чтобы также предотвратить сбой инициализации.
val
с помощьюlazy val
. Ваше утверждение, что вы не смогли бы творить,F3
если быbar
было a,val
неверно. Тем не менее, абстрактные члены в traits всегда должны бытьdef
sval schoko = bar + bar
наlazy val schoko = bar + bar
. Это один из способов контроля над порядком инициализации. Кроме того, использованиеlazy val
вместоdef
в производном классе позволяет избежать повторного вычисления.val bar: Int
наdef bar: Int
Fail.schoko
, все равно ноль.Я предпочитаю не использовать
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 ожидает возврата того же типа.источник
Всегда использовать def кажется немного неудобным, так как что-то вроде этого не сработает:
trait Entity { def id:Int} object Table { def create(e:Entity) = {e.id = 1 } }
Вы получите следующую ошибку:
error: value id_= is not a member of Entity
источник
var
. Дело в том, что если это поля, то они должны быть обозначены как таковые. Я просто считаю, что иметь все такdef
же недальновидно.var
давайте сломаем инкапсуляцию. Но использованиеdef
(илиval
) предпочтительнее глобальной переменной. Я думаю, что то, что вы ищете, похоже на то,case class ConcreteEntity(override val id: Int) extends Entity
что вы можете создать его из.def create(e: Entity) = ConcreteEntity(1)
Это безопаснее, чем нарушение инкапсуляции и разрешение любому классу изменять Entity.