インターフェースに「オプションのメソッド」を使用してJavaコレクションが実装されたのはなぜですか?


69

Javaコレクションフレームワークを拡張する最初の実装中に、オプションとして宣言されたメソッドがコレクションインターフェイスに含まれているのを見て驚いた。実装者は、サポートされていない場合、UnsupportedOperationExceptionsをスローすることが期待されています。これはすぐに、貧弱なAPIデザインの選択肢として私を驚かせました。

Joshua Blochの優れた「Effective Java」の本を多く読んだ後、彼がこれらの決定に責任を負う可能性があることを知った後、本で支持されている原則と一致しなかったようです。コレクションと、「オプション」メソッドでコレクションを拡張するMutableCollectionの2つのインターフェイスを宣言すると、はるかに保守性の高いクライアントコードにつながると思います。

ここに問題の優れた要約があります。

2つのインターフェイスの実装の代わりにオプションのメソッドが選択された理由はありますか?


IMO、正当な理由はありません。C ++ STLは、80年代初期にStepanovによって作成されました。その使いやすさは扱いにくいC ++テンプレート構文によって制限されていましたが、Javaコレクションクラスと比較すると、一貫性と使いやすさの象徴です。
ケビンクライン

JavaへのC ++ STLの「移植」がありました。しかし、クラス数は5倍でした。このことを念頭に置いて、大きい方がより一貫性があり使いやすいとは思いません。docs.oracle.com/javase/6/docs/technotes/guides/collections/...
m3th0dman

@ m3th0dman JavaはC ++テンプレートと同等の機能を持たないため、Javaのほうがはるかに大きくなります。
ケビンクライン14年

多分それは私が開発したちょっと変わったスタイルかもしれませんが、私のコードはコレクションを実際に作成するいくつかのメソッドを除いて、すべてのコレクションを「読み取り専用」(より正確には、カウントまたは反復するもの)として扱う傾向があります。とにかく良い方法でしょう(特に並行性のため)。そして、非常に多くの文句オプションの方法がいる決して私にとって本当の問題ではありません。また、「スーパー」と「エクステンド」を含むジェネリック医薬品の悪夢は、これまで(多くの)問題ではありませんでした。他の人がこの一般的な慣行を使用しているのだろうか?
user949300

回答:


28

よくある質問には、答えを提供します。つまり、実装可能なオプションメソッドの各セットについて、変更可能、変更不可能なビュー、削除専用、追加専用、固定長、不変(スレッド用)など、必要なインターフェイスの組み合わせの爆発の可能性がありました。


6
JavaにconstC ++のようなキーワードがあれば、これはすべて回避されていただろう。
エティエンヌデマルテル

@Etienne:より良いのは、Pythonのようなメタクラスです。その後、プログラムで組み合わせの数のインターフェイスを構築できます。constの問題は、次の1つまたは2つの次元しか提供しないことですvector<int>, const vector<int>, vector<const int>, const vector<const int>。これまでのところは良いが、その後など、あなたがグラフを実装してみてください、とあなたはグラフ構造を一定にしたいが、ノードが変更可能属性
ニール・G

2
@ Etienne、ToDoリストに「Learn Scala」を追加するもう1つの理由です。
グレンビュージェフ

2
私が理解していないのは、なぜ彼らがcan操作が可能かどうかをテストするメソッドを作成しなかったのですか?インターフェイスをシンプルかつ高速に保ちます。
-Mehrdad

3
@Etienne de Martelナンセンス。組み合わせ爆発でどのように役立ちますか?
トムホーティン-タックライン

10

Interface Segregation Principle当時は今ほど探求されていなかったように思えます。SOLIDとISPが品質コードの事実上の標準になる前は、その方法(つまり、インターフェイスにはすべての可能な操作が含まれており、不要なものに対して例外をスローする「縮退」メソッドがあります)。


2
なぜ下票なのか?ISPが気に入らない人はいますか?
ウェインモリナ

分散をサポートするフレームワークでは、基本的に不変なものと共変または反変なインターフェイスの側面を分離することには大きな利点があることにも注意してください。このようなサポートがない場合でも、型消去を使用しないフレームワークでは、型に依存しないインターフェイスの側面を分離することに価値があります(たとえば、Count含まれるアイテムの種類を気にせずにコレクションを取得できます)。しかし、Javaのような型消去ベースのフレームワークでは、このような問題はありません。
-supercat

4

一部の人々は「オプションのメソッド」を嫌うかもしれませんが、多くの場合、高度に分離されたインターフェースよりも優れたセマンティクスを提供します。とりわけ、オブジェクトは、その存続期間中に能力や特性を獲得したり、オブジェクト(特にラッパーオブジェクト)がどの正確な能力を報告すべきかをいつ構築するかを知らなかったりする可能性を考慮します。

優れたデザインのJavaコレクションクラスのパラゴンを呼び出すことはほとんどありませんが、優れたコレクションフレームワークには、その特性と能力についてコレクションに問い合わせる方法と共に、多数のオプションメソッドを基盤に含めることをお勧めします。このような設計により、基になるコレクションが持つ可能性のある機能を誤って不明瞭にすることなく、単一のラッパークラスをさまざまなコレクションで使用できます。メソッドがオプションではない場合、コレクションがサポートする機能の組み合わせごとに異なるラッパークラスを用意するか、状況によっては使用できないラッパーを用意する必要があります。

たとえば、コレクションがインデックスによるアイテムの書き込みまたは末尾へのアイテムの追加をサポートするが、アイテムの中央への挿入をサポートしない場合、実行されるすべてのアクションをログに記録するラッパーにそれをカプセル化するコードにはバージョンが必要ですサポートされている機能の正確な組み合わせを提供するロギングラッパーの場合、または利用できない場合は、appendまたはwrite-by-indexのいずれかをサポートし、両方はサポートしないラッパーを使用する必要があります。ただし、統合コレクションインターフェイスが3つのメソッドをすべて「オプション」として提供し、どのオプションメソッドが使用可能かを示すメソッドを含めると、単一のラッパークラスが機能の任意の組み合わせを実装するコレクションを処理できます。どの機能をサポートしているのかを尋ねられると、ラッパーはカプセル化されたコレクションがサポートしているものを単に報告できます。

「オプションの機能」の存在により、集約されたコレクションが、機能が実装の存在によって定義された場合よりもはるかに効率的な方法で特定の機能を実装できる場合があります。たとえば、concatenateメソッドを使用して他の2つの複合コレクションを形成するとします。最初のコレクションはたまたま1,000,000個の要素を持つArrayListであり、最後のコレクションは最初から反復できる20個の要素のコレクションでした。複合コレクションが1,000,013番目の要素(インデックス1,000,012)を要求された場合、ArrayListに含まれるアイテムの数(つまり1,000,000)を要求し、要求されたインデックスからそれを減算し(12を取得)、2番目から12の要素を読み取り、スキップできますコレクション、および次の要素を返します。

このような状況では、複合コレクションにはインデックスによってアイテムを瞬時に返す方法はありませんが、複合コレクションに1,000,013番目のアイテムを要求する方が、個々のアイテムから1,000,013個のアイテムを読み取り、最後を除くすべてを無視するよりもはるかに高速です1。


@kevincline:引用された言葉遣いに反対しますか?インターフェースの実装がその本質的な特性と能力を記述することができる手段の設計は、あまり注目されていない場合でも、インターフェース設計の最も重要な側面の1つであると考えます。インターフェースの異なる実装が特定の共通タスクを達成するための異なる最適な方法を持っている場合、パフォーマンスを気にするクライアントが重要なシナリオで最適なアプローチを選択する手段が必要です。
supercat 14年

1
申し訳ありませんが、コメントが不完全です。私は、「「コレクションについて尋ねる方法」...」と言っていましたが、コンパイル時のチェックが必要なものをランタイムに移動します。
ケビンクライン14年

@kevincline:クライアントが特定の能力を必要とし、それなしでは生きられない場合、コンパイル時のチェックが役立ちます。一方、コレクションがコンパイル時保証できる以上の能力を持つ可能性がある多くの状況があります。場合によっては、派生インターフェイスのすべての正当な実装が基本インターフェイスでオプションである特定のメソッドをサポートする必要があることを契約で指定している派生インターフェイスを持つことが理にかなっていますが、コードが何かを処理できるかどうかにかかわらずそれはいくつかの機能...持っている
supercat

...しかし、その機能の恩恵を受けるでしょう、オブジェクトの型が機能をサポートすることを約束するかどうかを型システムに尋ねるよりも、コードが機能をサポートするかどうかをオブジェクトに尋ねる方が良いです。特定の「特定の」インターフェースが広く使用さAsXXXれる場合、そのインターフェースを実装する場合、呼び出されるオブジェクトを返すメソッドをベースインターフェースに含めるか、可能であればそのインターフェースをサポートするラッパーオブジェクトを返すか、スローしますそうでない場合は例外。例えば、ImmutableCollectionインタフェースは...契約で必要な場合があります
supercat

2
あなたの提案には問題があります。オブジェクトの機能がその存続中に変化する可能性がある場合、「ask then do」パターンには並行性の問題があります。(とにかく例外を危険にさらす必要がある場合は、わざわざ尋ねる必要はありません...)
jhominal

-1

当時よく知らなかった元の開発者に起因すると思います。Java 2とCollectionsが最初にリリースされた1998年前後から、OO設計で大きな進歩を遂げました。明らかな悪いデザインのように見えるものは、OOPの初期にはそれほど明白ではありませんでした。

ただし、余分なキャストを防ぐために行われた可能性があります。それが2番目のインターフェイスである場合、コレクションインスタンスをキャストして、これらのオプションメソッドを呼び出す必要がありますが、これもまたugいです。今ではUnsupportedOperationExceptionをすぐにキャッチして、コードを修正します。ただし、2つのインターフェイスがある場合は、instanceofとキャストをすべての場所で使用する必要があります。おそらく彼らはそれを妥当なトレードオフだと考えたのでしょう。また、Java 2の初期の頃、instanceofはパフォーマンスが遅いためにひどく嫌われていました。彼らはそれを使いすぎないようにしようとしていました。

もちろん、これはすべて野生の憶測です。元のコレクションの建築家の誰もがチャイムを鳴らさない限り、私たちはこれに確実に答えることができるとは思いません。


7
何のキャスティング?メソッドがCollectiona MutableCollectionではなくaを返す場合、それは変更されることを意図していないことを明確に示しています。なぜ誰かがそれらをキャストする必要があるのか​​分かりません。別個のインターフェースを持つことは、実行時に例外を取得する代わりに、コンパイル時にこれらの種類のエラーを取得することを意味します。エラーを早く取得するほど、良い結果が得られます。
エティエンヌデマルテル

1
これは柔軟性が低いため、コレクションライブラリの最大の利点の1つは、実際の実装を心配せずに、あらゆる場所で高レベルのインターフェイスを返すことができることです。2つのインターフェイスを使用している場合は、より緊密に結合されています。ほとんどの場合、ImmutableListではなく、単にListを返したいだけです。通常は、それを呼び出しクラスに任せて決定するためです。
Jberg

6
読み取り専用のコレクションを提供するのは、それを変更したくないからです。例外をスローし、ドキュメントに依存することは、パッチのように酷い感じがします。C ++では、constオブジェクトを返すだけで、オブジェクトを変更できないことをユーザーに即座に伝えます。
エティエンヌデマルテル

7
1998年は「初期のオブジェクト指向設計」ではありませんでした。
quant_dev
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.