のインターフェイスに完全に汎用的な get メソッドを持たないという決定の背後にある理由は何ですかjava.util.Map<K, V>
。
質問を明確にするために、メソッドの署名は
V get(Object key)
それ以外の
V get(K key)
なぜだろうと思っています( についても同じですremove, containsKey, containsValue
)。
のインターフェイスに完全に汎用的な get メソッドを持たないという決定の背後にある理由は何ですかjava.util.Map<K, V>
。
質問を明確にするために、メソッドの署名は
V get(Object key)
それ以外の
V get(K key)
なぜだろうと思っています( についても同じですremove, containsKey, containsValue
)。
get()
他の人が述べたように、取得するエントリのキーがに渡すオブジェクトと同じタイプである必要がないため、などの理由は一般的ではありませんget()
。メソッドの仕様は、それらが等しいことのみを必要とします。これはequals()
、オブジェクトと同じ型だけでなく、メソッドがオブジェクトをパラメーターとして受け取る方法に由来します。
equals()
多くのクラスが、そのオブジェクトがそれ自身のクラスのオブジェクトとのみ等しくなるように定義されていることは一般的に正しいかもしれませんが、Java にはそうでない場所がたくさんあります。たとえば、 の仕様でList.equals()
は、 の実装が異なっていても、2 つの List オブジェクトが両方とも List であり、内容が同じであれば、それらは等しいとされていList
ます。この質問の例に戻ると、メソッドの仕様によれば、引数としてaMap<ArrayList, Something>
を呼び出すことができ、同じ内容のリストであるキーを取得する必要があります。これは、ジェネリックで引数の型が制限されている場合は不可能です。get()
LinkedList
get()
Google の素晴らしい Java コーダーである Kevin Bourrillion は、この問題について少し前のブログ投稿Set
で (確かにではなく のコンテキストでMap
) 書いています。最も関連性の高い文:
一様に、Java Collections Framework (および Google Collections Library も) のメソッドは、コレクションが壊れないようにする必要がある場合を除いて、パラメーターの型を制限することはありません。
原則としてそれに同意するかどうかは完全にはわかりません.NETは、たとえば、正しいキータイプを必要とするのに問題ないようですが、ブログ投稿の理由に従う価値があります. (.NET について言及したので、それが .NET で問題にならない理由の一部は、.NET にはより限定された分散のより大きな問題があるということを説明する価値があります...)
契約は次のように表現されます。
より正式には、このマップに (key==null ? k==null : key.equals(k) ) となるキー k から値 v へのマッピングが含まれている場合 、このメソッドは v を返します。それ以外の場合は null を返します。(このようなマッピングは最大で 1 つ存在できます。)
(私の強調)
そのため、キー検索の成功は、入力キーの等価メソッドの実装に依存します。それは必ずしもk のクラスに依存するわけではありません。
これはポステルの法則「自分のすることは保守的であり、他人から受け入れることは寛大であれ」の応用です。
等値チェックは型に関係なく実行できます。equals
メソッドはクラスで定義され、任意のパラメーターObject
を受け入れます。そのため、キーの等価性、およびキーの等価性に基づく操作が任意の型Object
を受け入れることは理にかなっています。Object
マップがキー値を返す場合、type パラメーターを使用して、できるだけ多くの型情報を保存します。
Generics Tutorial のこのセクションで状況が説明されていると思います (強調):
「ジェネリック API が過度に制限されていないことを確認する必要があります。API の元のコントラクトを引き続きサポートする必要があります。java.util.Collection の例をもう一度考えてください。プレジェネリック API は次のようになります。
interface Collection {
public boolean containsAll(Collection c);
...
}
それを生成する単純な試みは次のとおりです。
interface Collection<E> {
public boolean containsAll(Collection<E> c);
...
}
これは確かにタイプ セーフですが、API の元の規約には準拠していません。 containsAll() メソッドは、あらゆる種類の受信コレクションで機能します。受信コレクションに実際に E のインスタンスのみが含まれている場合にのみ成功しますが、次のようになります。
その理由は、containment が と によって決定されequals
、hashCode
どちらが on のメソッドであり、どちらもパラメーターObject
を取るためです。Object
これは、Java の標準ライブラリの初期の設計上の欠陥でした。Java の型システムの制限と相まって、equals と hashCode に依存するものはすべて強制的にObject
.
Java でタイプ セーフなハッシュ テーブルと等価性を実現する唯一の方法は、一般的な代用を回避Object.equals
して使用することです。Functional Javaには、まさにこの目的のための型クラスが付属しています:および. コンストラクターでandを受け取るラッパーが提供されます。したがって、このクラスとメソッドは type のジェネリック引数を取ります。Object.hashCode
Hash<A>
Equal<A>
HashMap<K, V>
Hash<K>
Equal<K>
get
contains
K
例:
HashMap<String, Integer> h =
new HashMap<String, Integer>(Equal.stringEqual, Hash.stringHash);
h.add("one", 1);
h.get("one"); // All good
h.get(Integer.valueOf(1)); // Compiler error
もう1つ重大な理由があります。それは技術的に不可能です。マップを壊してしまったからです。
Java には、 のようなポリモーフィックなジェネリック構造があり<? extends SomeClass>
ます。そのような参照をマークすると、 で署名されたタイプを指すことができます<AnySubclassOfSomeClass>
。しかし、ポリモーフィックジェネリックはその参照をreadonlyにします。コンパイラは、ジェネリック型をメソッドの戻り型としてのみ使用することを許可しますが (単純なゲッターのように)、ジェネリック型が引数であるメソッドの使用をブロックします (通常のセッターのように)。つまり、 と書くMap<? extends KeyType, ValueType>
と、コンパイラはメソッドの呼び出しを許可せずget(<? extends KeyType>)
、マップは役に立たなくなります。唯一の解決策は、このメソッドをジェネリックではなくすることです: get(Object)
.
後方互換性、私は推測します。Map
(またはHashMap
) まだ をサポートする必要がありますget(Object)
。