Collection.remove(Object o) ジェネリックではないのはなぜですか?
_Collection<E>
_はboolean remove(E o);
を持つことができるようです
次に、誤って(たとえば)_Set<String>
_の個々の文字列ではなく_Collection<String>
_を削除しようとすると、後でデバッグの問題が発生する代わりにコンパイル時エラーになります。
Josh BlochとBill Pughは、この問題について Java Puzzlers IV:The Phantom Reference Menace、Attack of the Clone、およびRevenge of The Shift 。
Josh Bloch氏は(6:41)Mapのgetメソッドを生成し、メソッドなどを削除しようとしたが、「単に機能しなかった」と述べています。
パラメータタイプとしてコレクションのジェネリックタイプのみを許可する場合、ジェネレートできない合理的なプログラムが多すぎます。彼によって与えられた例は、List
sのNumber
とList
sのLong
の交点です。
remove()
(Map
およびCollection
内)は汎用ではありません。これは、あらゆるタイプのオブジェクトをremove()
に渡すことができるためです。削除されるオブジェクトは、remove()
に渡すオブジェクトと同じタイプである必要はありません。それらが等しいことだけが必要です。 remove()
の仕様から、remove(o)
は、_(o==null ? e==null : o.equals(e))
_がe
になるようにオブジェクトtrue
を削除します。 o
とe
を同じ型にする必要はありません。これは、equals()
メソッドがオブジェクトと同じ型ではなく、Object
をパラメーターとして受け取るという事実から得られます。
ただし、多くのクラスではequals()
が定義されているため、そのオブジェクトは自分のクラスのオブジェクトとのみ等しくなることがよくありますが、必ずしもそうとは限りません。たとえば、List.equals()
の仕様では、2つのListオブジェクトが両方ともListであり、List
の異なる実装であっても同じ内容である場合、等しいとされています。この質問の例に戻ると、_Map<ArrayList, Something>
_を持つことができ、LinkedList
を引数としてremove()
を呼び出すことができ、キーを削除する必要がありますこれは同じ内容のリストです。 remove()
がジェネリックであり、引数タイプが制限されている場合、これは不可能です。
Typeパラメーターがワイルドカードである場合、汎用のremoveメソッドを使用できないためです。
Mapのget(Object)メソッドでこの質問にぶつかったことを思い出すようです。この場合のgetメソッドはジェネリックではありませんが、最初の型パラメーターと同じ型のオブジェクトが渡されることを合理的に期待する必要があります。最初の型パラメーターとしてワイルドカードを使用してMapsを渡す場合、その引数がジェネリックである場合、そのメソッドを使用してMapから要素を取得する方法はありません。コンパイラは型が正しいことを保証できないため、ワイルドカード引数は実際に満たすことはできません。 addが汎用的である理由は、コレクションに追加する前に型が正しいことを保証することが期待されるためだと推測します。ただし、オブジェクトを削除するときに、タイプが正しくない場合、とにかく一致しません。引数がワイルドカードの場合、前の行で参照したため、そのコレクションに属するオブジェクトを保証できる場合でも、メソッドは使用できません。
私はおそらくあまり上手く説明しなかったかもしれませんが、それは私には十分に理にかなっているようです。
他の答えに加えて、メソッドがObject
(述語)を受け入れる必要がある別の理由があります。次のサンプルを検討してください。
class Person {
public String name;
// override equals()
}
class Employee extends Person {
public String company;
// override equals()
}
class Developer extends Employee {
public int yearsOfExperience;
// override equals()
}
class Test {
public static void main(String[] args) {
Collection<? extends Person> people = new ArrayList<Employee>();
// ...
// to remove the first employee with a specific name:
people.remove(new Person(someName1));
// to remove the first developer that matches some criteria:
people.remove(new Developer(someName2, someCompany, 10));
// to remove the first employee who is either
// a developer or an employee of someCompany:
people.remove(new Object() {
public boolean equals(Object employee) {
return employee instanceof Developer
|| ((Employee) employee).company.equals(someCompany);
}});
}
}
ポイントは、remove
メソッドに渡されるオブジェクトがequals
メソッドの定義を担当することです。このようにして、述語の構築が非常に簡単になります。
Cat
のコレクションと、Animal
、Cat
、SiameseCat
、およびDog
型のオブジェクト参照があるとします。コレクションにCat
またはSiameseCat
参照によって参照されるオブジェクトが含まれているかどうかを確認するのは理にかなっています。 Animal
参照によって参照されるオブジェクトが含まれているかどうかを尋ねることは危険に思えるかもしれませんが、それでも完全に合理的です。結局のところ、問題のオブジェクトはCat
であり、コレクションに表示される可能性があります。
さらに、たとえオブジェクトがCat
以外であっても、コレクションに表示されるかどうかは問題ありません。単に「いいえ、ありません」と答えてください。あるタイプの「ルックアップスタイル」コレクションは、任意のスーパータイプの参照を有意義に受け入れ、オブジェクトがコレクション内に存在するかどうかを判断できる必要があります。渡されたオブジェクト参照が関連のないタイプの場合、コレクションがそれを含む可能性はないため、クエリはある意味で意味がありません(常に「no」と応答します)。それでも、パラメーターをサブタイプまたはスーパータイプに制限する方法はないため、コレクションのタイプとは関係のないオブジェクトについては、単にタイプを受け入れて「no」と答えるのが最も実用的です。
これは、remove()が与えるオブジェクトの種類を気にする理由がないためだと常に考えていました。いずれにせよ、equals()を呼び出すことができるので、そのオブジェクトがCollectionに含まれているオブジェクトの1つであるかどうかを確認するのは簡単です。 add()で型をチェックして、その型のオブジェクトのみが含まれていることを確認する必要があります。
妥協でした。どちらのアプローチにも利点があります。
remove(Object o)
remove(E e)
は、誤ってshortのリストから整数を削除しようとするなど、コンパイル時に微妙なバグを検出することで、ほとんどのプログラムが実行したいタイプ安全性を高めます。Java APIを進化させる場合、常に後方互換性が主要な目標でした。したがって、既存のコードの生成が容易になるため、remove(Object o)が選択されました。デザイナーがremove(E e)を選択したと推測します。