のインターフェースに完全に汎用的な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
ます。この質問に戻っ例にくるので、この方法の仕様に応じて持つことが可能であるMap<ArrayList, Something>
と私は、呼び出すためget()
にLinkedList
、引数として、それは同じ内容のリストであるキーを取得する必要があります。get()
ジェネリックで引数の型が制限されている場合、これは不可能です。
m.get(linkedList)
、なぜm
の型を次のように定義しなかったのですMap<List,Something>
か?インターフェイスを取得するm.get(HappensToBeEqual)
ためにMap
タイプを変更せずに呼び出すのが理にかなっているユースケースは考えられません。
TreeMap
間違ったタイプのオブジェクトをメソッドに渡すと失敗する可能性get
がありますが、マップがたまたま空である場合など、ときどき渡す可能性があります。さらに悪いことに、指定されComparator
たcompare
メソッドの場合(ジェネリックシグネチャを持つ!)は、チェックされていない警告なしに間違った型の引数で呼び出される可能性があります。これは壊れた動作です。
GoogleのすばらしいJavaコーダーであるKevin Bourrillionは、この問題について少し前にブログ投稿で(確かにではSet
なくのコンテキストでMap
)書いています。最も関連性の高い文:
一様に、Java Collections Framework(およびGoogle Collections Libraryも)のメソッドは、コレクションが壊れないようにする必要がある場合を除いて、パラメーターのタイプを決して制限しません。
原則として同意するかどうかは完全にはわかりません。たとえば、.NETは適切なキーの種類を要求することで問題ないようですが、ブログ投稿の推論に従う価値はあります。(.NETについて言及したので、それが.NETで問題にならない理由の一部は、分散がより限定された.NETでより大きな問題があるということです...)
Integer
とa Double
が互いに等しくなることは決してありませんが、a Set<? extends Number>
に値が含まれているかどうかを尋ねるのは依然として公正な質問ですnew Integer(5)
。
Set<? extends Foo>
。私は非常に頻繁にマップのキータイプを変更しましたが、コードの更新が必要なすべての場所をコンパイラが見つけられなかったのでイライラしました。これが正しいトレードオフであると私は本当に確信していません。
契約はこのように表現されます:
より正式には、このマップにキーkから値vへのマッピング(key == null?k == null: key.equals(k))が含まれている場合、このメソッドはvを返します。それ以外の場合はnullを返します。(このようなマッピングは多くても1つしかありません。)
(私の強調)
したがって、キーの検索が成功するかどうかは、入力メソッドの等価メソッドの実装に依存します。これは必ずしも kのクラスに依存しているわけではありません。
hashCode()
ます。hashCode()を適切に実装しequals()
ないと、この場合、適切に実装しても意味がありません。
get()
に型の引数を取る必要はありませんObject
。getメソッドがキータイプに制限されていると想像してくださいK
-コントラクトはまだ有効です。もちろん、コンパイル時の型がのサブクラスではない場所での使用はコンパイルにK
失敗しますが、コントラクトはコードをコンパイルするとどうなるかを暗黙的に議論するため、コントラクトが無効になることはありません。
それはポステルの法則の応用であり、「あなたがすることを保守的にし、他人から受け入れることを寛大にしてください」です。
タイプに関係なく、等価性チェックを実行できます。equals
この方法は、上に定義されObject
たクラスと、任意の受け入れObject
パラメータとして。したがって、キーの等価性、およびキーの等価性に基づく操作では、任意のObject
タイプを受け入れることが理にかなっています。
マップがキー値を返す場合、マップはtypeパラメータを使用して、できるだけ多くのタイプ情報を保存します。
V Get(K k)
も理にかなっているので、C#にあります。Javaと.NETのアプローチの違いは、実際には一致しないものをブロックする人だけです。C#ではコンパイラ、Javaではコレクションです。I間での.NETの一貫性のないコレクションクラスに一度程度の怒りが、Get()
そしてRemove()
唯一確かに一致するタイプを受け入れることは、偶然に間違った値を渡すのを防ぐことができます。
contains : K -> boolean
です。
ジェネリックチュートリアルのこのセクションでは、状況を説明していると思います(私の強調):
「ジェネリック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のインスタンスのみが含まれている場合にのみ成功しますが、次のようになります。
containsAll( Collection< ? extends E > c )
、なぜですか?
containsAll
が、Collection<S>
where S
がのスーパータイプであることを許可する必要もありE
ます。もしそうなら、これは許可されませんcontainsAll( Collection< ? extends E > c )
。また、としてれる明示的例に記載されている、それは(戻り値は、その後された状態で、異なるタイプのコレクションを渡す正当ですfalse
)。
その理由は、包含はメソッドによって決定されequals
、hashCode
どちらがメソッドでObject
あり、どちらもObject
パラメーターを取るためです。これは、Javaの標準ライブラリの初期の設計上の欠陥でした。Javaの型システムの制限と相まって、equalsとhashCodeに依存するものはすべて強制的に使用しますObject
。
Javaでタイプセーフなハッシュテーブルと平等を持っている唯一の方法は避けることですObject.equals
し、Object.hashCode
そして一般的な代替物を使用しています。機能Javaはまさにこの目的のために型クラスが付属していますHash<A>
とEqual<A>
。用のラッパーHashMap<K, V>
が提供されてHash<K>
おりEqual<K>
、そのコンストラクター内で使用されます。したがってget
、このクラスとcontains
メソッドはtypeのジェネリック引数を取り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
互換性。
ジェネリックが利用可能になる前は、get(Object o)しかありませんでした。
彼らがこのメソッドをget(<K> o)に変更した場合、作業コードを再度コンパイルするためだけに、Javaユーザーに大規模なコードメンテナンスを強制する可能性があります。
彼らは可能性が導入されている追加的な方法を、get_checked言う(<K> O)と穏やかな移行パスがあったので、古いのget()メソッドを非推奨。しかし、何らかの理由で、これは行われませんでした。(現在の状況では、findBugsなどのツールをインストールして、get()引数とマップの宣言されたキータイプ<K>のタイプの互換性を確認する必要があります。)
.equals()のセマンティクスに関連する引数は偽物だと思います。(厳密にはそれらは正しいですが、私はまだそれらは偽物だと思います。彼の正しい考えでは、o1とo2に共通のスーパークラスがない場合、o1.equals(o2)をtrueにするつもりはありません。)
もう1つ大きな理由があります。Mapが壊れているため、技術的には実行できません。
Javaには、のような多態的な汎用構造があり<? extends SomeClass>
ます。このような参照をマークすると、で署名された型を指すことができ<AnySubclassOfSomeClass>
ます。しかし、ポリモーフィックなジェネリックはその参照を読み取り専用にします。コンパイラでは、ジェネリック型をメソッドの戻り型(単純なゲッターなど)としてのみ使用できますが、ジェネリック型が引数であるメソッド(通常のセッターなど)の使用はブロックされます。つまり、を記述したMap<? extends KeyType, ValueType>
場合、コンパイラーはmethodを呼び出すget(<? extends KeyType>)
ことができず、マップは役に立たなくなります。唯一の解決策は、このメソッドをジェネリックにしないことですget(Object)
。
下位互換性があると思います。Map
(またはHashMap
)まだサポートする必要がありますget(Object)
。
put
(これはジェネリック型を制限します)。raw型を使用すると、下位互換性が得られます。ジェネリックは「オプトイン」です。
私はこれを見て、なぜ彼らがこのようにそれをしたのかを考えていました。既存の回答のどれもが、新しいジェネリックインターフェイスに適切なタイプのキーのみを受け入れさせられなかった理由を説明しているとは思いません。実際の理由は、ジェネリックを導入しても、新しいインターフェイスを作成しなかったためです。Mapインターフェースは古いジェネリックでないMapと同じで、ジェネリックバージョンと非ジェネリックバージョンの両方として機能します。このようにして、非ジェネリックMapを受け入れるメソッドがある場合Map<String, Customer>
、それを渡すことができ、それでも機能します。同時に、getのコントラクトはObjectを受け入れるため、新しいインターフェイスもこのコントラクトをサポートする必要があります。
私の意見では、新しいインターフェイスを追加して既存のコレクションに両方を実装する必要がありましたが、たとえgetメソッドの設計が悪いことを意味するとしても、互換性のあるインターフェイスを支持することにしました。コレクション自体は既存のメソッドと互換性があることに注意してください。インターフェイスだけは互換性がありません。
今、大きなリファクタリングを行っていますが、この強く型付けされたget()が欠落していたため、古い型のget()が欠落していないことを確認しました。
しかし、コンパイル時間チェックの回避策/醜いトリックを見つけました:強く型付けされたget、containsKey、removeを使用してMapインターフェースを作成し、プロジェクトのjava.utilパッケージに配置します。
get()を呼び出すだけでコンパイルエラーが発生します...間違った型を使用すると、他のすべてのものはコンパイラーに問題がないようです(少なくともeclipse kepler内)。
これはランタイムで必要なものではないため、ビルドのチェック後にこのインターフェースを削除することを忘れないでください。