特性の代わりに抽象クラスを使用する利点は何ですか(パフォーマンスは別として)?ほとんどの場合、抽象クラスは特性に置き換えることができるようです。
私は2つの違いを考えることができます
プログラミングにはScalaに "特性を付けるか、特性を付けないか?" というセクションがあり、この質問に対処しています。第1版はオンラインで入手できるので、ここですべてを引用してもいいと思っています。 (深刻なScalaプログラマーは本を買うべきです):
再利用可能な動作のコレクションを実装するときはいつでも、特性クラスを使用するか抽象クラスを使用するかを決定する必要があります。確固たる規則はありませんが、このセクションには考慮すべきいくつかのガイドラインが含まれています。
動作が再利用されない場合、具体的なクラスにします。結局、再利用可能な動作ではありません。
複数の無関係なクラスで再利用される可能性がある場合、それを特性にします。クラス階層の異なる部分に混合できるのは、特性のみです。
Java codeで継承する場合は、抽象クラスを使用します。コードを持つ特性には類似したJavaアナログがないため、Javaクラスの特性から継承するのは面倒な傾向があります。一方、Scalaクラスから継承することは、Javaクラスから継承することとまったく同じです。 1つの例外として、抽象メンバーのみを持つScala特性はJavaインターフェースに直接変換されるため、Javaを期待していても、このような特性を自由に定義できます。それから継承するコード。 JavaとScalaの併用の詳細については、第29章を参照してください。
それをコンパイルされた形式で配布することを計画している場合、外部グループがそれを継承するクラスを書くことを期待する場合、抽象クラスを使用することに傾くかもしれません。問題は、トレイトがメンバーを獲得または失った場合、それを継承するクラスは変更されていなくても再コンパイルする必要があるということです。外部のクライアントがビヘイビアを継承するのではなく、ビヘイビアのみを呼び出す場合は、特性を使用するのが適切です。
効率が非常に重要な場合、クラスの使用に傾倒します。ほとんどのJavaランタイムは、クラスメンバーの仮想メソッド呼び出しを、インターフェイスメソッド呼び出しよりも高速な操作にします。特性はインターフェースにコンパイルされるため、わずかなパフォーマンスオーバーヘッドが発生する場合があります。ただし、この選択は、問題の特性がパフォーマンスのボトルネックを構成していることを知っており、代わりにクラスを使用すると実際に問題が解決するという証拠がある場合にのみ選択してください。
まだわからない場合、上記を考慮した後、それを特性として作成することから始めます。後からいつでも変更できます。一般に、特性を使用すると、より多くのオプションが開いたままになります。
@Mushtaq Ahmedが述べたように、トレイトにはクラスのプライマリコンストラクターにパラメーターを渡すことはできません。
もう1つの違いは、super
の処理です。
クラスと特性のもう1つの違いは、クラスでは
super
呼び出しが静的にバインドされるのに対して、特性では動的にバインドされることです。クラスにsuper.toString
を記述すると、どのメソッド実装が呼び出されるかが正確にわかります。ただし、トレイトに同じことを書くと、トレイトを定義するときに、スーパーコールのために呼び出すメソッド実装は未定義になります。
詳細については、残りの 第12章 を参照してください。
Edit 1(2013):
抽象クラスの特性は、特性と比較して微妙な違いがあります。線形化ルールの1つは、クラスの継承階層を保持することです。これにより、抽象クラスをチェーンの後半にプッシュする傾向がありますが、特性は喜んで混在させることができます。 、そのため抽象クラスを使用できます。 Scalaのクラスの線形化(ミックスイン順序)の制約 を参照してください。
Edit 2(2018):
Scala 2.12の時点で、traitのバイナリ互換性の動作が変更されました。 2.12より前では、クラスが変更されていない場合でも、トレイトへのメンバーの追加または削除には、トレイトを継承するすべてのクラスの再コンパイルが必要でした。これは、JVMでの特性のエンコード方法によるものです。
Scala 2.12現在、特性 Javaインターフェイスにコンパイル であるため、要件は少し緩和されています。特性が次のいずれかを実行する場合、そのサブクラスは依然として再コンパイルが必要です。
- フィールドの定義(
val
またはvar
、ただし、定数は問題ありません–結果型なしのfinal val
)super
の呼び出し- 本文内の初期化ステートメント
- クラスを拡張する
- 適切なスーパートレイトの実装を見つけるために線形化に依存
ただし、特性がそうでない場合は、バイナリ互換性を損なうことなく更新できます。
価値があるものは何でも、Odersky et alの Scalaでのプログラミング は、疑うときは特性を使用することを推奨しています。必要に応じて、後からいつでも抽象クラスに変更できます。
複数の抽象クラスを直接拡張することはできませんが、複数の特性をクラスにミックスインできるという事実を除き、特性のスーパーコールは動的にバインドされているため、特性はスタッカブルであることに注意してください現在のもの)。
抽象クラスと特性の違い のトーマスの答えから:
trait A{
def a = 1
}
trait X extends A{
override def a = {
println("X")
super.a
}
}
trait Y extends A{
override def a = {
println("Y")
super.a
}
}
scala> val xy = new AnyRef with X with Y
xy: Java.lang.Object with X with Y = $anon$1@6e9b6a
scala> xy.a
Y
X
res0: Int = 1
scala> val yx = new AnyRef with Y with X
yx: Java.lang.Object with Y with X = $anon$1@188c838
scala> yx.a
X
Y
res1: Int = 1
抽象クラスを拡張する場合、これはサブクラスが同様の種類であることを示しています。形質を使用する場合、これは必ずしもそうではありません。
Programming Scala で、著者は、抽象クラスは古典的なオブジェクト指向の「is-a」関係を作り、特性は構成のスカラーウェイであると言います。
抽象クラスは動作を含むことができます-コンストラクター引数でパラメーター化でき(特性ではできない)、作業エンティティを表すことができます。代わりに、特性は、1つの機能、1つの機能のインターフェイスを表すだけです。