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


371

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

回答:


371

2つの違いが考えられます

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

172
非常に重要な補遺:クラスは複数の特性から継承できますが、抽象クラスは1つだけです。これは、ほとんどすべての場合にどちらを使用するかを検討するときに開発者が最初に尋ねる質問だと思います。
BAR

15
命の恩人:「トレイトは、実装コードが含まれていない場合にのみ完全に相互運用可能」
Walrus the Cat

2
抽象-オブジェクト(オブジェクトのブランチ)を定義または導くが、まだ(準備ができている)オブジェクトとして構成されていない集合的な動作。特性は、機能を導入する必要がある場合、つまり機能がオブジェクトの作成から発生することはない場合、オブジェクトが分離から出て通信する必要があるときに進化または必要になります。
Ramiz Uddin、2015年

5
2番目の違いはJava8には存在しないと思います。
Duong Nguyen

14
Scala 2.12 に従い、トレイトはJava 8インターフェースにコンパイルされます-scala-lang.org/news/2.12.0#traits-compile-to-interfaces
ケビンメレディス2017年

209

Scalaでのプログラミングには、「特性を評価するか、評価しないか」というセクションがあります。この質問に対処します。第1版はオンラインで入手できるので、ここですべてを引用してもかまいません。(Scalaの真面目なプログラマは本を買うべきです):

再利用可能な動作のコレクションを実装するときは常に、特性または抽象クラスのどちらを使用するかを決定する必要があります。明確な規則はありませんが、このセクションには、考慮すべきいくつかのガイドラインが含まれています。

動作が再利用されない場合は、それを具象クラスにします。結局それは再利用可能な振る舞いではありません。

複数の無関係なクラス再利用される可能性がある場合は、それをトレイトにします。クラス階層のさまざまな部分に混在できるのは、特性のみです。

Javaコード継承する場合は、抽象クラスを使用します。コード付きのトレイトにはJavaの類似物がないため、Javaクラスのトレイトから継承するのは面倒です。一方、Scalaクラスからの継承は、Javaクラスからの継承とまったく同じです。1つの例外として、抽象メンバーのみのScalaトレイトはJavaインターフェースに直接変換されるため、Javaコードが継承することを期待している場合でも、そのようなトレイトを自由に定義してください。JavaとScalaの併用の詳細については、第29章を参照してください。

コンパイルされた形式配布する予定で、外部のグループがそれを継承するクラスを作成することを期待している場合は、抽象クラスを使用する傾向があります。問題は、トレイトがメンバーを取得または失ったとき、継承されたクラスは、変更されていなくても再コンパイルする必要があることです。外部のクライアントがビヘイビアから継承するのではなく、ビヘイビアを呼び出すだけの場合は、トレイトを使用することで問題ありません。

効率が非常に重要な場合は、クラスの使用に傾けます。ほとんどのJavaランタイムは、クラスメンバーの仮想メソッド呼び出しを、インターフェイスメソッド呼び出しよりも高速な操作にします。トレイトはインターフェースにコンパイルされるため、わずかなパフォーマンスオーバーヘッドが発生する可能性があります。ただし、この選択は、問題の特性がパフォーマンスのボトルネックを構成していることがわかっており、代わりにクラスを使用すると実際に問題が解決するという証拠がある場合にのみ選択してください。

それでもわからない場合は、上記を検討した後、まずは特性として作成してください。後でいつでも変更できます。通常、トレイトを使用すると、より多くのオプションが開いたままになります。

@Mushtaq Ahmedが述べたように、トレイトはクラスのプライマリコンストラクターに渡されるパラメーターを持つことができません。

もう1つの違いは、の扱いですsuper

クラスと特性のもう1つの違いは、クラスでsuperは呼び出しが静的にバインドされるのに対し、特性では呼び出しが動的にバインドされることです。super.toStringクラスを記述する場合、どのメソッド実装が呼び出されるかが正確にわかります。ただし、同じことを特性で記述した場合、特性を定義するときに、スーパー呼び出しのために呼び出すメソッド実装は未定義です。

詳細については、第12章の残りの部分を参照してください。

編集1(2013):

抽象クラスの動作は、特性と比較して微妙に異なります。線形化ルールの1つは、クラスの継承階層を保持することです。これにより、抽象クラスをチェーンの後半にプッシュする傾向がありますが、特性をうまく組み合わせることができます。特定の状況では、クラス線形化の後の位置にあることが実際に望ましいですなので、そのために抽象クラスを使用できます。Scalaのクラスの線形化の制約(混合順)を参照してください。

編集2(2018):

Scala 2.12以降、トレイトのバイナリ互換動作が変更されました。2.12より前のバージョンでは、クラスを変更していなくても、メンバーを特性に追加または削除するには、特性を継承するすべてのクラスの再コンパイルが必要でした。これは、JVMでの特性のエンコード方法が原因です。

Scala 2.12以降、トレイトはJavaインターフェイスコンパイルされるため、要件は少し緩和されました。トレイトが次のいずれかを行う場合でも、そのサブクラスは再コンパイルが必要です。

  • フィールドの定義(valまたはvar、ただし定数は問題ありfinal valません- 結果タイプなし)
  • 呼び出す super
  • 本文の初期化ステートメント
  • クラスを拡張する
  • 線形化に依存して正しいスーパートレイトの実装を見つける

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


2
If outside clients will only call into the behavior, instead of inheriting from it, then using a trait is fine-ここで違いは何ですか?extendswith
2015年

2
@ 0fnt彼の区別は、拡張と比較ではありません。彼が言っているのは、同じコンパイル内で特性を混ぜるだけの場合、バイナリ互換性の問題は当てはまらないということです。ただし、APIがユーザーがトレイト自体を混ぜることができるように設計されている場合は、バイナリ互換性について心配する必要があります。
John Colanduoni

2
@ 0fnt:間全く意味的な違いがあるextendsとはwith。それは純粋に構文です。複数のテンプレートから継承する場合、最初のextendget with、他のすべてのget 、それだけです。withコンマと考えてください:class Foo extends Bar, Baz, Qux
イェルクWミッターク


20

複数の抽象クラスを直接拡張することはできませんが、複数の特性を1つのクラスに混在させることはできますが、特性のスーパー呼び出しは動的にバインドされるため、特性はスタック可能であることを言及する価値があります(以前に混合されたクラスまたは特性を参照しています)現在のもの)。

抽象クラスと特性の違いにおけるトーマスの答えから:

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

9

抽象クラスを拡張する場合、これはサブクラスが同様の種類であることを示しています。トレイトを使用する場合、これは必ずしもそうではない、と私は思います。


これには実際的な意味がありますか、それともコードを理解しやすくするだけですか?
Ralf


5

抽象クラスは振る舞いを含むことができます-それらはコンストラクターの引数(特性はできません)でパラメーター化でき、作業エンティティーを表します。代わりに、特性は単一の機能、つまり1つの機能のインターフェースを表します。


8
特性が振る舞いを含むことができないことをあなたが示唆していないことを願っています。どちらにも実装コードを含めることができます。
Mitch Blevins、2010年

1
@Mitch Blevins:もちろん違います。それらにはコードを含めることができますが、trait Enumerable多くのヘルパー関数で定義する場合、それらを動作と呼ぶのではなく、1つの機能に関連付けられた機能のみと呼びます。
Dario

4
@Dario「振る舞い」と「機能性」は同義語だと思うので、あなたの答えは非常に混乱します。
David J.

3
  1. クラスは複数の特性から継承できますが、抽象クラスは1つだけです。
  2. 抽象クラスには、型パラメーターだけでなくコンストラクターパラメーターも含めることができます。特性は型パラメーターのみを持つことができます。たとえば、特性t(i:Int){}とは言えません。iパラメータは不正です。
  3. 抽象クラスはJavaと完全に相互運用できます。ラッパーなしでJavaコードから呼び出すことができます。トレイトは、実装コードが含まれていない場合にのみ、完全に相互運用できます。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.