JavaコレクションAPIの設計に関するFAQ
このドキュメントは、Java Collections Frameworkの設計に関するよくある質問とその回答です。 質問と回答は、collections-commentsエイリアス上の膨大なトラフィックから選出しました。 Collections Frameworkの設計原理として参考にできます。
コア・インタフェース - 一般的な質問
- オプションのオペレーション (およびUnsupportedOperationException)を排除できるように、コア・コレクション・インタフェースで不変性を直接サポートしてはどうでしょうか。
- UnsupportedOperationExceptionを発行する場合、オプションのオペレーションを呼び出すコードをtry-catch節の中に入れる必要はないのですか。
- 「バッグ」(マルチセットとも呼ぶ)のためのコア・インタフェースがないのはなぜですか。
- 一貫性を得るために「Bean方式の名前」を使わなかったのはなぜですか。
Collectionインタフェース
- CollectionがCloneableとSerializableを継承しないのはなぜですか。
- Collectionのすべての要素に既知のメソッド(upcall)を適用するためにapplyメソッドをCollectionで提供してはどうでしょうか。
- Predicateインタフェースと関連メソッド(述部を満たすCollection内の最初の要素を検索するメソッドなど)がないのはなぜですか。
- addAllメソッドにEnumeration (またはIterator)をとる形式を提供しないのはなぜですか。
- JDKの固定実装にEnumeration (またはIterator)のコンストラクタがないのはなぜですか。
- Iterator.addメソッドがないのはなぜですか。
Listインタフェース
- ListインタフェースをSequenceに改名しないのはなぜですか。listは一般的に「リンク・リスト」を示唆するのではありませんか。また、java.awt.Listと矛盾しませんか。
- Setとの混乱を防ぐために、Listのsetメソッドをreplaceに改名しないのはなぜですか。
Mapインタフェース
Iteratorインタフェース
雑
- JDKにJGL (ObjectSpace, Inc. の既存のコレクション・パッケージ)を採用せずに新しいCollections Frameworkを作ったのはなぜですか。
- ビュー(コレクションに似た別のオブジェクトに連動するコレクション)を返すメソッドとクラスをすべて廃止しないのはなぜですか。そうすれば、エイリアシングが大幅に削減されるでしょう。
- 修正されたときにイベントを送出する被監視コレクションを提供しないのはなぜですか。
コア・インタフェース - 一般的な質問
- オプションのオペレーション (およびUnsupportedOperationException)を排除できるように、コア・コレクション・インタフェースで不変性を直接サポートしてはどうでしょうか。
これは、API全体でもっとも議論の余地のある設計上の決定事項です。 静的な(コンパイル時の)型チェックが望ましいのは明らかで、Javaの標準でもあります。 可能ならばサポートしたかったのですが、 これをサポートするとインタフェース階層のサイズが非常に大きくなり、実行時の例外の必要性を(かなり減らすことはできても)なくすことができません。
Doug Leaは、インタフェース階層に可変性の特徴を反映したポピュラなJavaコレクションを作成しました。しかし、そのコレクション・パッケージのユーザーとしての経験から、このアプローチはもはや実行可能ではないと考えています。 彼は、私信の中で「これを言うのはつらいが、強力な静的型設定はJavaのコレクション・インタフェースでは機能しない」と述べています。
問題を詳しく説明するために、階層に修正可能性という概念を追加しようとする場合について考えます。 この場合、ModifiableCollection、ModifiableSet、ModifiableList、ModifiableMapの4つの新しいインタフェースが必要になります。 以前は単純だった階層が複雑になります。 また、削除オペレーションを持たない変更不可能なコレクションで使用するための新しいイテレータが必要になります。 この状態で、UnsupportedOperationExceptionを排除することはできるでしょうか。 残念ながらできません。
配列について考えます。 配列にはほとんどのListオペレーションが備わっていますが、削除と追加のオペレーションはありません。 配列は「固定サイズ」のListです。 階層にこの概念を与えようとすると、VariableSizeListとVariableSizeMapの2つの新しいインタフェースが必要になります。 VariableSizeCollectionとVariableSizeSetは、ModifiableCollectionとModifiableSetに等しいので追加する必要はありませんが、一貫性のために追加しなければならない場合があります。 また、変更不可能なListに対処するため、追加と削除のオペレーションをサポートしない新種のListIteratorが必要です。 最初に必要だったのは4つのインタフェースのみでしたが、このように10 - 12個のインタフェースに加えて2個の新しいイテレータ・インタフェースが必要になります。 これで十分でしょうか。 不可
ログ(エラー・ログ、監査ログ、修復可能なデータ・オブジェクト用のジャーナル)について考えます。 これらは、本質的に追加専用シーケンスで、削除と設定(置換)以外のすべてのListオペレーションをサポートします。 ログには、新しいコア・インタフェースと新しいイテレータが必要になります。
変更不可能なコレクションとは対照的な、不変のコレクション(つまり、クライアントによる変更が不可能で、かつ他のどのような理由によっても変化しないコレクション)についてはどうでしょうか。 多くの人が、これがもっとも重要な特徴であると主張しています。その理由は、複数のスレッドが(同期の必要なく)同時に1つのコレクションにアクセスできるからです。 型の階層にこれを追加するには、さらに4つのインタフェースが必要です。
これで、インタフェースの数は最大20個程度、イテレータは5個になりました。実際には、明らかにどのインタフェースにも適合しないコレクションが増えているのはほぼ確実です。 たとえば、Mapから返されるコレクション・ビューは本質的に削除専用コレクションです。 また、値に基づいて特定の要素を拒否するコレクションもあるので、実行時の例外を排除することはできません。
議論と試行の末、実行時例外を発行できるコア・インタフェースのごく小さなセットを提供することで問題全体を回避することが、道理にかなった技術的妥協案であるとの結論に達しました。
- UnsupportedOperationExceptionを発行する場合、オプションのオペレーションを呼び出すコードをtry-catch節の中に入れる必要はないのですか。
プログラムでこれらの例外をキャッチする必要はないと考えています。これらの例外がチェックされない例外(実行時例外)なのは、そのためです。 これらの例外は、プログラミング・エラーの結果としてのみ発生するため、この場合は、例外がキャッチされないためにプログラムが機能停止します。
- 「バッグ」(マルチセットとも呼ぶ)のためのコア・インタフェースがないのはなぜですか。
Collectionインタフェースにこの機能があります。 このインタフェースがほかのインタフェースのように頻繁に利用されるとは考えられないので、publicの実装はしていません。 AbstractCollection (たとえば、Map.valuesから返されるコレクション)の上に簡単に実装されるようなコレクションを随時返します。
- 一貫性を得るために「Bean方式の名前」を使わなかったのはなぜですか。
新しいコレクション・メソッドの名前は「Beanの命名規約」には従っていませんが、妥当で一貫性があり、目的に適した名前だと考えています。 Beanの命名規約は、JDK全体に適用されているわけではありません。AWTはこれらの規約を採用しましたが、この決断は議論を呼ぶものでした。 コレクションAPIは、きわめて広範に使用されるので、1行のコードで複数のメソッドが呼び出されることが多く、名前が短いことが重要だと考えます。 たとえば、Iteratorメソッドについて考えてみましょう。 現在、1つのコレクションに関するループは次のようなものです。
for (Iterator i = c.iterator(); i.hasNext(); ) System.out.println(i.next());
コレクションの名前が長い場合でも、すべてが1行に収まります。 メソッドの名前をgetIterator、hasNextElement、getNextElementとした場合、1行には収まりません。 このような理由から、Bean方式ではなく「従来の」JDK方式を採用しました。
Collectionインタフェース
- CollectionがCloneableとSerializableを継承しないのはなぜですか。
多くのCollection実装(JDKが供給するものもすべて含め)にはpublicのcloneメソッドがありますが、すべてのCollectionにこのメソッドを要求するのは誤りです。 たとえば、1テラバイトのSQLデータベースを基にするCollectionのクローンを作るとはどういうことでしょうか。 メソッドを呼び出すことにより、会社が新しいディスク装置ファームを請求するようなことになっても良いのでしょうか。 同様な議論は、いくらでもあります。
クライアントがCollectionの実際の型を知らない場合、クライアントがどの型のCollectionが必要なのかを決定し、その型の空のCollectionを作成し、addAllメソッドを使って元のコレクションの要素を新しいコレクションにコピーする方が、より柔軟性が高くエラーが起こりにくくなります。
- Collectionのすべての要素に既知のメソッド(upcall)を適用するためにapplyメソッドをCollectionで提供してはどうでしょうか。
これは、『Design Patterns』(Gammaほか著)という本で「Internal Iterator」と呼ばれているものです。 これを提供することについては考慮しましたが、内部および外部イテレータをサポートするのは冗長であると思われ、またJavaにはすでに外部イテレータ用の機能(Enumerationに対して)があるので、提供しないことに決めました。 アップ・コールを記述するためにpublicのインタフェースが必要であるため、この機能の「投射重量」は増大してしまうのです。
- Predicateインタフェースと関連メソッド(述部を満たすCollection内の最初の要素を検索するメソッドなど)がないのはなぜですか。
Iteratorの上にこの機能を実装するのは簡単で、ユーザーが述語を直列に並べることができるため、結果のコードはより簡潔になります。 しかし、この簡潔さが十分役に立つかどうかは明確ではありません。 役立つと思われる場合は、Collectionクラスにあとからこれを追加(Iteratorの上に実装)することができます。
- addAllメソッドにEnumeration (またはIterator)をとる形式を提供しないのはなぜですか。
「代用のコレクション」としてEnumeration (またはIterator)を使用しない方が良いと考えるからです。 この使用方法は、以前のリリースではしばしば行われていましたが、今回のリリースではCollectionインタフェースがあるので、オブジェクトの抽象コレクションを次々にまわす方が良い方法です。
- JDKの固定実装にEnumeration (またはIterator)のコンストラクタがないのはなぜですか。
これも、「代用のコレクション」としてのEnumerationの使用方法であり、私たちはこの使用方法の防止に努めています。 逆に、すべての固定実装にはCollectionをとるコンストラクタを持たせることを(また、同じ要素で新しいCollectionを作成することを)強くお薦めします。
- Iterator.addメソッドがないのはなぜですか。
Iteratorの規約が反復の順序に関して何の保証も行わないとすれば、意味があいまいになります。 ただし、ListIteratorでは反復の順序が保証されるので、追加オペレーションが利用できます。
Listインタフェース
- ListインタフェースをSequenceに改名しないのはなぜですか。listは一般的に「リンク・リスト」を示唆するのではありませんか。また、java.awt.Listと矛盾しませんか。
Listがリンク・リストを示唆するかどうかの意見は二分しています。 実装命名規約を<Implementation><Interface>と仮定すると、コア・インタフェースの名前は短いままにしたいという強い要望があります。 また、ListをSequenceに変更すると、既存の名前のいくつか(AbstractSequentialList, LinkedList)は、不適切なものになります。 命名の矛盾は、次の「形式」で対処できます。
import java.util.*; import java.awt.*; import java.util.List; // Dictates interpretation of "List"
- Setとの混乱を防ぐために、Listのsetメソッドをreplaceに改名しないのはなぜですか。
この言語では、set/get命名規約が正式であると決まっているので、それに従いました。
Mapインタフェース
- MapがCollectionを継承しないのはなぜですか。
これは設計によるものです。 マッピングはコレクションではなく、コレクションはマッピングではないと考えています。 そのため、MapがCollectionインタフェースを継承する(その逆も)ということにはほとんど意味がありません。
MapがCollectionだとすれば、要素は何でしょうか。 唯一の妥当な答えは「キーと値のペア」ですが、これでは非常に限定された(しかも特に役に立つわけでもない) Mapの抽象化しかできません。 与えられたキーがどんな値にマップするかを問い合わせることも、与えられたキーがどんな値にマップするかを知らずにそのキーのエントリを削除することもできません。
CollectionがMapを継承するようにはできますが、「キーとは何か」という問題が発生します。 これには満足できる答えはなく、無理に答えを探しても、結果的には不自然なインタフェースになります。
Mapは(キー、値、またはキーと値のペアの) Collectionとして表示できます。このことは、Mapの3つの「コレクション表示オペレーション」(keySet、entrySet、values)に反映されています。 原理的には、Listを要素へのMapマッピング・インデックスとして表示することはできますが、Listから要素を削除すると、削除された要素の前のすべての要素に関連するKeyが変更されるという厄介な性質があります。 Listにマップ表示オペレーションがないのは、この理由からです。
Iteratorインタフェース
- IteratorがEnumerationを継承しないのはなぜですか。
Enumerationのメソッド名は不適切なものだと考えています。 これらは非常に長く、頻繁に使用されます。 メソッドを追加して新しいフレームワーク全体を作成する立場から、名前を改善する機会は利用すべきであると考えました。 もちろん、Iteratorで新しい名前と古い名前の両方をサポートすることもできますが、それは価値のあることとは思われませんでした。
- イテレータを進めないで反復の次の要素を見ることができる、Iterator.peekメソッドを提供しないのはなぜですか。
そのメソッドは、java.io.PushbackInputStreamと似た方法で、現在のIteratorの上に実装できます。 このメソッドを使用することはめったにないので、だれもが実装する必要のあるインタフェース内に含める価値はないと考えます。
雑
- JDKにJGL (ObjectSpace, Inc. の既存のコレクション・パッケージ)を採用せずに新しいCollections Frameworkを作ったのはなぜですか。
Collections Frameworkの設計目標(「Collections Frameworkの概要」にある)を読むと、JGLとまったく同じ立場では作業していないことが理解できると思います。 Javaコレクション概要の「設計目標」セクションから引用すると、「設計上の主要な目標は、実際のサイズにおいて、またより重要性の高い「概念の重さ」においても妥当な小ささのAPIを作成することでした」という記述があります。
JGLは約130のクラスとインタフェースで構成され、主な目標はC++STL (Standard Template Library)との整合性でした。 これは、目標ではありませんでした。 Javaは従来、C++の持つ複雑な特徴(多重継承、演算子のオーバーロードなど)を避けてきました。 すべてのインフラストラクチャを含め、フレームワークには全体で約25のクラスとインタフェースがあります。
C++のプログラマの中にはこのことを不快に感じる人がいるかもしれませんが、長期的にはJavaにとって良いことだと思います。 Javaのライブラリが成熟するにつれて増大するのは避けられませんが、Javaが容易に学習できて楽しく使える言語でありつづけられるよう、小ささと管理しやすさをできるだけ維持するように努力しています。
- ビュー(コレクションに似た別のオブジェクトに連動するコレクション)を返すメソッドとクラスをすべて廃止しないのはなぜですか。そうすれば、エイリアシングが大幅に削減されるでしょう。
プログラマが自分の実装を「隠す」ことのできるコア・コレクション・インタフェースを提供すると、別名のコレクションが(それをJDKが提供するしないにかかわらず)存在することになります。 JDKからのすべてのビューを廃止すると、たとえば配列からCollectionを作成するような共通のオペレーションのコストが増大し、多くの有用な機能(同期ラッパーなど)が廃止されるでしょう。 特に有用だと考えられているビューの1つは、List.subListです。 このメソッドの存在は、入力にListをとるメソッドを書く場合に、(配列の場合のように)オフセットと長さをとるセカンダリ形式を書く必要がないということを意味します。
- 修正されたときにイベントを送出する被監視コレクションを提供しないのはなぜですか。
主に、リソースの制約によります。 そのようなAPIに取り組むのであれば、万人に役立つものでなければならず、また長期の使用に耐えるものでなければなりません。 このような機能も提供していく予定です。 それまでの間、公開APIの上にそのような機能を実装するのは難しいことではありません。
Copyright © 1998, 2017, Oracle and/or its affiliates. 500 Oracle Parkway
Redwood Shores, CA 94065 USA. All rights reserved.