A def
można zaimplementować za pomocą a def
, a val
, a lazy val
lub an object
. Jest to więc najbardziej abstrakcyjna forma definiowania członka. Ponieważ cechy są zwykle abstrakcyjnymi interfejsami, stwierdzenie, że chcesz, val
to powiedzenie, jak powinna działać implementacja. Jeśli poprosisz o a val
, klasa implementująca nie może użyć def
.
A val
jest potrzebne tylko wtedy, gdy potrzebujesz stabilnego identyfikatora, np. Dla typu zależnego od ścieżki. To jest coś, czego zwykle nie potrzebujesz.
Porównać:
trait Foo { def bar: Int }
object F1 extends Foo { def bar = util.Random.nextInt(33) }
class F2(val bar: Int) extends Foo // ok
object F3 extends Foo {
lazy val bar = {
Thread.sleep(5000)
42
}
}
Gdybyś miał
trait Foo { val bar: Int }
nie byłbyś w stanie zdefiniować F1
lub F3
.
OK, i żeby zmylić Cię i odpowiedzieć @ om-nom-nom - użycie abstrakcyjnych val
s może powodować problemy z inicjalizacją:
trait Foo {
val bar: Int
val schoko = bar + bar
}
object Fail extends Foo {
val bar = 33
}
Fail.schoko
Jest to brzydki problem, który moim zdaniem powinien zniknąć w przyszłych wersjach Scali poprzez naprawienie go w kompilatorze, ale tak, obecnie jest to również powód, dla którego nie należy używać abstrakcyjnych val
s.
Edycja (styczeń 2016 r.): Możesz zastąpić abstrakcyjną val
deklarację lazy val
implementacją, aby również zapobiec niepowodzeniu inicjalizacji.
val
pliklazy val
. Twoje twierdzenie, że nie byłbyś w stanie stworzyć,F3
gdybybar
było a,val
nie jest poprawne. Powiedział, że członkowie w abstrakcyjnych cech powinna być zawszedef
„sval schoko = bar + bar
golazy val schoko = bar + bar
. To jeden ze sposobów uzyskania pewnej kontroli nad kolejnością inicjalizacji. Ponadto użycielazy val
zamiastdef
w klasie pochodnej pozwala uniknąć ponownego obliczania.val bar: Int
na,def bar: Int
Fail.schoko
nadal wynosi zero.Wolę nie używać
val
w cechach, ponieważ deklaracja val ma niejasną i nieintuicyjną kolejność inicjalizacji. Możesz dodać cechę do już działającej hierarchii, a to zepsułoby wszystkie rzeczy, które działały wcześniej, zobacz mój temat: dlaczego używać zwykłego val na zajęciach niedokończonychPowinieneś mieć na uwadze wszystkie rzeczy związane z używaniem tych deklaracji val, które ostatecznie prowadzą do błędu.
Zaktualizuj za pomocą bardziej skomplikowanego przykładu
Ale są chwile, kiedy nie można było uniknąć używania
val
. Jak wspomniał @ 0__, czasami potrzebujesz stabilnego identyfikatora, a nimdef
nie jest.Podałbym przykład, aby pokazać, o czym mówił:
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 }
Ten kod powoduje błąd:
StableIdentifier.scala:14: error: stable identifier required, but Access2.this.holder found. def access : holder.Inner =
Jeśli zastanowisz się przez chwilę, zrozumiesz, że kompilator ma powód do narzekania. W
Access2.access
przypadku, gdy nie może w żaden sposób wyprowadzić typu zwracanego.def holder
oznacza, że można by go wdrożyć na szeroką skalę. Mógłby zwrócić różnych posiadaczy dla każdego połączenia, a posiadacze mogliby zawierać różneInner
typy. Ale maszyna wirtualna Java oczekuje zwrócenia tego samego typu.źródło
Zawsze używanie def wydaje się trochę niezręczne, ponieważ coś takiego nie zadziała:
trait Entity { def id:Int} object Table { def create(e:Entity) = {e.id = 1 } }
Otrzymasz następujący błąd:
error: value id_= is not a member of Entity
źródło
var
. Chodzi o to, że jeśli są to pola, to powinny być tak oznaczone. Po prostu myślę, że wszystkodef
jest krótkowzroczne.var
let's you break encapsulation. Ale użycie adef
(lub aval
) jest preferowane zamiast zmiennej globalnej. Myślę, że to, czego szukasz, jest czymś w rodzajucase class ConcreteEntity(override val id: Int) extends Entity
, abyś mógł to utworzyć zdef create(e: Entity) = ConcreteEntity(1)
To jest bezpieczniejsze niż zerwanie hermetyzacji i zezwolenie dowolnej klasie na zmianę Entity.