私は effective scala slides を行っていましたが、スライド10では、抽象メンバーのval
でtrait
を使用せず、代わりにdef
を使用するように言及しています。 val
で抽象trait
を使用することがアンチパターンである理由を詳細に説明します。
def
は、def
、val
、lazy val
、またはobject
のいずれかで実装できます。したがって、これは、メンバーを定義する最も抽象的な形式です。トレイトは通常抽象的なインターフェースであるため、val
が欲しいということはhowと言っていることです。 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
sを使用すると初期化の問題が発生する可能性があります。
trait Foo {
val bar: Int
val schoko = bar + bar
}
object Fail extends Foo {
val bar = 33
}
Fail.schoko // zero!!
これはい問題であり、個人的な意見では将来的にはなくなるはずですScala=コンパイラで修正することでバージョンが、はい、現在、これは抽象val
s。
編集(2016年1月):lazy val
実装で抽象val
宣言をオーバーライドできるため、初期化の失敗も防止できます。
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仮想マシンは同じタイプが返されることを期待しています。