web-dev-qa-db-ja.com

特性の代わりに抽象クラスを使用する利点は何ですか?

特性の代わりに抽象クラスを使用する利点は何ですか(パフォーマンスは別として)?ほとんどの場合、抽象クラスは特性に置き換えることができるようです。

351
Ralf

私は2つの違いを考えることができます

  1. 抽象クラスには、型パラメーターだけでなくコンストラクターパラメーターも含めることができます。特性は型パラメーターのみを持つことができます。将来的にはトレイトでもコンストラクターパラメーターを持つことができるという議論がありました
  2. 抽象クラスは、Javaと完全に相互運用可能です。ラッパーなしでJavaコードから呼び出すことができます。特性は、実装コードが含まれていない場合にのみ完全に相互運用可能です
351
Mushtaq Ahmed

プログラミングには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の呼び出し
  • 本文内の初期化ステートメント
  • クラスを拡張する
  • 適切なスーパートレイトの実装を見つけるために線形化に依存

ただし、特性がそうでない場合は、バイナリ互換性を損なうことなく更新できます。

192
Eugene Yokota

価値があるものは何でも、Odersky et alの Scalaでのプログラミング は、疑うときは特性を使用することを推奨しています。必要に応じて、後からいつでも抽象クラスに変更できます。

75

複数の抽象クラスを直接拡張することはできませんが、複数の特性をクラスにミックスインできるという事実を除き、特性のスーパーコールは動的にバインドされているため、特性はスタッカブルであることに注意してください現在のもの)。

抽象クラ​​スと特性の違い のトーマスの答えから:

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
19
Nemanja Boric

抽象クラスを拡張する場合、これはサブクラスが同様の種類であることを示しています。形質を使用する場合、これは必ずしもそうではありません。

9
peter p

Programming Scala で、著者は、抽象クラスは古典的なオブジェクト指向の「is-a」関係を作り、特性は構成のスカラーウェイであると言います。

7
CQQL

抽象クラスは動作を含むことができます-コンストラクター引数でパラメーター化でき(特性ではできない)、作業エンティティを表すことができます。代わりに、特性は、1つの機能、1つの機能のインターフェイスを表すだけです。

5
Dario
  1. クラスは複数の特性から継承できますが、抽象クラスは1つだけです。
  2. 抽象クラスには、型パラメーターだけでなくコンストラクターパラメーターも含めることができます。特性は型パラメーターのみを持つことができます。たとえば、特性t(i:Int){};とは言えません。 iパラメーターは無効です。
  3. 抽象クラスは、Javaと完全に相互運用可能です。ラッパーなしでJavaコードから呼び出すことができます。特性は、実装コードが含まれていない場合にのみ完全に相互運用可能です。
1
pavan.vn101