Java

Java コレクション API の設計に関する FAQ


このページは、Java Collections Framework の設計に関するよくある質問とその回答です。 質問と回答は、collections-comments エイリアス上の膨大なトラフィックから選出しました。 Collections Framework の設計原理として参考にできます。

コアインタフェース - 一般的な質問

  1. 省略可能オペレーション (および UnsupportedOperationException 例外) を排除できるように、コアコレクションで不変性を直接サポートしてはどうでしょうか。

  2. UnsupportedOperationException を発行する場合、省略可能オペレーションを呼び出すコードを try-catch ブロックの中に入れる必要はないのですか。

  3. 「バッグ」(マルチセットとも呼ぶ) のためのコアインタフェースがないのはなぜですか。

  4. 型保証されたコレクションの実装を簡単にする「ゲート機能」は提供しないのですか。

  5. 一貫性を得るために「Beans 方式の名前」を使わなかったのはなぜですか。

Collection インタフェース

  1. Collection が Cloneable と Serializable を継承しないのはなぜですか。

  2. Collection のすべての要素に既知のメソッド (upcall) を適用するために apply メソッドを Collection で提供してはどうでしょうか。

  3. Predicate インタフェースと関連メソッド (述部を満たす Collection 内の最初の要素を検索するメソッドなど) がないのはなぜですか。

  4. addAll メソッドに Enumeration (または Iterator) をとる形式を提供しないのはなぜですか。

  5. JDK の固定実装に Enumeration (または Iterator) の コンストラクタがないのはなぜですか。

  6. Iterator.add メソッドがないのはなぜですか。

List インタフェース

  1. List インタフェースを Sequence に改名しないのはなぜですか。 list は一般的に「リンクリスト」を示唆するのではありませんか。 また、java.awt.List と矛盾しませんか。

  2. Set との混乱を防ぐために、List の set メソッドを replace に改名しないのはなぜですか。

Map インタフェース

  1. Map が Collection を継承しないのはなぜですか。

Iterator インタフェース

  1. Iterator が Enumeration を継承しないのはなぜですか。

  2. 反復子を進めないで反復の次の要素を見ることができる、Iterator.peek メソッドを提供しないのはなぜですか。

その他

  1. JDK に JGL (ObjectSpace, Inc. の既存のコレクションパッケージ) を採用せずに新しい Collections Framework を作ったのはなぜですか。

  2. ビュー (コレクションに似た別のオブジェクトから返されるコレクション) を返すメソッドとクラスをすべて廃止しないのはなぜですか。 廃止すれば、エイリアシングを大幅に削減できると思いますが。

  3. 修正されたときにイベントを送出する被監視コレクションを提供しないのはなぜですか。


コアインタフェース - 一般的な質問

  1. 省略可能オペレーション (および 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 から返されるコレクションビューは本質的に削除専用コレクションです。 また、値に基づいて特定の要素を拒否するコレクションもあるので、実行時の例外を排除することはできません。

    議論と試行の末、実行時例外を発行できるコアインタフェースのごく小さなセットを提供することで問題全体を回避することが、道理にかなった技術的妥協案であるとの結論に達しました。

  2. UnsupportedOperationException を発行する場合、省略可能オペレーションを呼び出すコードを try-catch ブロックの中に入れる必要はないのですか。

    プログラムでこれらの例外をキャッチする必要はないと考えています。 これらの例外がチェックされない例外 (実行時例外) なのは、そのためです。 これらの例外は、プログラミングエラーの結果としてだけ発生するため、この場合は、例外がキャッチされないためにプログラムが機能停止します。

  3. 「バッグ」(マルチセットとも呼ぶ) のためのコアインタフェースがないのはなぜですか。

    Collection インタフェースにこの機能があります。 このインタフェースが他のインタフェースのように頻繁に利用されるとは考えられないので、public の実装はしていません。 AbstractCollection (たとえば、Map.values から返されるコレクション) の上に簡単に実装されるようなコレクションを随時返します。

  4. 型保証されたコレクションの実装を簡単にする「ゲート機能」は提供しないのですか。

    型保証されるコレクションの要望には大いに賛成します。 場当たり的な型の安全性を得るための応急処置をフレームワークに追加するのではなく、現在議論されているパラメータ化された型の提案すべてに適応するようにフレームワークは設計されています。 パラメータ化された型が言語に追加されたときには、すべてのコレクションのフレームワークが、明示的なキャストの必要がない「コンパイル時の」型安全性の確保をサポートします。 1.2 リリースではこれは実現しません。 それまでの間、実行時の型保証が必要な場合は、JDK コレクションを囲む「ラッパー」コレクションに独自のゲート機能を実装することができます。

  5. 一貫性を得るために「Beans 方式の名前」を使わなかったのはなぜですか。

    新しいコレクションメソッドの名前は「Beans の命名規約」には固執していませんが、妥当で一貫性があり、目的に適した名前だと考えています。 Beans の命名規約は、JDK 全体に適用されているわけではありません。 AWT はこれらの規約を採用しましたが、この決断は議論を呼ぶものでした。 コレクション API は、極めて広範に使用されるので、1 行のコードで複数のメソッドが呼び出されることが多く、名前が短いことが重要だと考えます。 たとえば、Iterator メソッドについて考えてみましょう。 現在、1 つのコレクションに関するループは次のようなものです。

        for (Iterator i = c.iterator(); i.hasNext(); )
            System.out.println(i.next());
    
    コレクションの名前が長い場合でも、すべてが 1 行に収まります。 メソッドの名前を getIterator、hasNextElement、getNextElement とした場合、1 行には収まりません。 このような理由から、Beans 方式ではなく「従来の」JDK 方式を採用しました。

Collection インタフェース

  1. Collection が Cloneable と Serializable を継承しないのはなぜですか。

    多くの Collection 実装 (JDK が供給するものもすべて含め) には public の clone メソッドがありますが、すべての Collection にこのメソッドを要求するのは誤りです。 たとえば、1 テラバイトの SQL データベースを基にする Collection の複製を作るとはどういうことでしょうか。 メソッドを呼び出すことにより、会社が新しいディスク装置ファームを請求するようなことになっても良いのでしょうか。 同様な議論は、いくらでもあります。

    クライアントが Collection の実際の型を知らない場合、クライアントがどの型の Collection が必要なのかを決定し、その型の空の Collection を作成し、addAll メソッドを使って元のコレクションの要素を新しいコレクションにコピーする方が、より柔軟性が高くエラーが起こりにくくなります。

  2. Collection のすべての要素に既知のメソッド (upcall) を適用するために apply メソッドを Collection で提供してはどうでしょうか。

    これは、『Design Patterns』(Gamma ほか著) という本で「Internal Iterator」と呼ばれているものです。 これを提供することについては考慮しましたが、内部および外部反復子をサポートするのは冗長であると思われ、また Java にはすでに外部反復子用の機能 (Enumeration に対して) があるので、提供しないことに決めました。 アップコールを記述するために public のインタフェースが必要であるため、この機能の「投射重量」は増大してしまうのです。

  3. Predicate インタフェースと関連メソッド (述部を満たす Collection 内の最初の要素を検索するメソッドなど) がないのはなぜですか。

    Iterator の上にこの機能を実装するのは簡単で、ユーザが述語を直列に並べることができるため、結果のコードはより簡潔になります。 しかし、この簡潔さが十分役に立つかどうかは明確ではありません。 役立つと思われる場合は、Collection クラスにあとからこれを追加 (Iterator の上に実装) することができます。

  4. addAll メソッドに Enumeration (または Iterator) をとる形式を提供しないのはなぜですか。

    「代用のコレクション」として Enumeration (または Iterator) を使用しない方が良いと考えるからです。 この使用方法は、以前のリリースではしばしば行われていましたが、今回のリリースでは Collention インタフェースがあるので、オブジェクトの抽象コレクションを次々にまわす方が良い方法です。

  5. JDK の固定実装に Enumeration (または Iterator) の コンストラクタがないのはなぜですか。

    これも、「代用のコレクション」としての Enumeration の使用方法であり、私たちはこの使用方法の防止に努めています。 逆に、すべての固定実装には Collection をとるコンストラクタを持たせることを (また、同じ要素で新しい Collection を作成することを) 強くお勧めします。

  6. Iterator.add メソッドがないのはなぜですか。

    Iterator の規約が反復の順序に関して何の保証も行わないとすれば、意味があいまいになります。 ただし、ListIterator では反復の順序が保証されるので、追加オペレーションが利用できます。


List インタフェース

  1. 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"
    

  2. Set との混乱を防ぐために、List の set メソッドを replace に改名しないのはなぜですか。

    この言語では、set/get 命名規約が正式であると決まっているので、それに従いました。


Map インタフェース

  1. Map が Collection を継承しないのはなぜですか。

    これは設計によるものです。 マッピングはコレクションではなく、コレクションはマッピングではないと考えています。 そのため、Map が Collection インタフェースを継承する (その逆も) ということにはほとんど意味がありません。

    Map が Collection だとすれば、要素は何でしょうか。 唯一の妥当な答えは「キーと値のペア」ですが、これでは非常に限定された (しかも特に役に立つわけでもない) Map の抽象化しかできません。 与えられたキーがどんな値にマップするかを問い合わせることも、与えられたキーがどんな値にマップするかを知らずにそのキーのエントリを削除することもできません。

    Collection が Map を継承するようにはできますが、「キーとは何か」という問題が発生します。 これには満足できる答えはなく、無理に答えを探しても、結果的には不自然なインタフェースになります。

    Map は (キー、値、またはキーと値のペアの) Collection として「表示」できます。 このことは、Map の 3 つの「コレクション表示オペレーション」 (keySet、entrySet、values) に反映されています。 原理的には、List を 要素への Map マッピングインデックスとして表示することはできますが、List から要素を削除すると、削除された要素の前のすべての要素に関連する Key が変更されるという厄介な性質があります。 List にマップ表示オペレーションがないのは、この理由からです。


Iterator インタフェース

  1. Iterator が Enumeration を継承しないのはなぜですか。

    Enumeration のメソッド名は不適切なものだと考えています。 これらは非常に長く、頻繁に使用されます。 メソッドを追加して新しいフレームワーク全体を作成する立場から、名前を改善する機会は利用すべきであると考えました。 もちろん、Iterator で新しい名前と古い名前の両方をサポートすることもできますが、それは価値のあることとは思われませんでした。

  2. 反復子を進めないで反復の次の要素を見ることができる、Iterator.peek メソッドを提供しないのはなぜですか。

    そのメソッドは、java.io.PushbackInputStream と似た方法で、現在の Iterator の上に実装できます。 このメソッドを使用することは滅多にないので、誰もが実装する必要のあるインタフェース内に含める価値はないと考えます。


その他

  1. SDK に JGL (ObjectSpace, Inc. の既存のコレクションパッケージ) を採用せずに新しい Collections Framework を作ったのはなぜですか。

    Collections Framework の設計目標 (「Collections Framework の概要」にある) を読むと、JGL とまったく同じ立場では作業していないことが理解できると思います。Java コレクション概要の「設計目標」から引用すると、「設計上の主要な目標は、実際のサイズにおいて、またより重要性の高い「概念の重さ」においても妥当な小ささの API を作成することでした」という記述があります。

    JGL は約 130 のクラスとインタフェースで構成され、主な目標は C++ STL (Standard Template Library) との整合性でした。 これは、Collections Framework の目標ではありませんでした。 Java は従来、C++ の持つ複雑な特徴 (多重継承、演算子のオーバーロードなど) を避けてきました。すべてのインフラストラクチャを含め、Collections Framework には全体で約 25 のクラスとインタフェースがあります。

    C++ のプログラマの中にはこのことを不快に感じる人がいるかもしれませんが、長期的には Java にとって良いことだと思います。 Java のライブラリが成熟するにつれて増大するのは避けられませんが、Java が容易に学習できて楽しく使える言語でありつづけられるよう、小ささと管理しやすさをできるだけ維持するように努力しています。

  2. ビュー (コレクションに似た別のオブジェクトから返されるコレクション) を返すメソッドとクラスをすべて廃止しないのはなぜですか。 廃止すれば、エイリアシングを大幅に削減できると思いますが。

    プログラマが自分の実装を「隠す」ことのできるコアコレクションインタフェースを提供すると、別名のコレクションが (それを JDK が提供するしないにかかわらず) 存在することになります。 JDK からのすべてのビューを廃止すると、たとえば配列から Collection を作成するような共通のオペレーションのコストが増大し、多くの有用な機能 (同期ラッパーなど) が廃止されるでしょう。 特に有用だと考えられているビューのひとつは、List.subList です。 このメソッドの存在は、入力に List をとるメソッドを書く場合に、(配列の場合のように) オフセットと長さをとる第 2 の形式を書く必要がないということを意味します。

  3. 修正されたときにイベントを送出する被監視コレクションを提供しないのはなぜですか。

    主に、リソースの制約によります。 そのような API に取り組むのであれば、万人に役立つものでなければならず、また長期の使用に耐えるものでなければなりません。 このような機能も提供していく予定です。 それまでの間、public の API の上にそのような機能を実装するのは難しいことではありません。


Copyright © 1997-1999 Sun Microsystems, Inc., 901 San Antonia Ave., Palo Alto, CA 94303 USA. All rights reserved.

コメントの送付先: collections-comments@java.sun.com
Sun
Java ソフトウェア