なぜではない Collection.remove(Object o)でジェネリックでですか?
のように思える Collection<E>
持っているboolean remove(E o);
次に、誤って(たとえば)Set<String>
から個々の文字列を削除しようとするCollection<String>
と、後でデバッグの問題が発生する代わりに、コンパイル時エラーになります。
なぜではない 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メソッド、removeメソッドなどを一般化しようとしたが、「うまくいかなかった」と述べている。
コレクションのジェネリック型のみをパラメーター型として許可する場合、一般化できない合理的なプログラムが多すぎます。彼が与えられた例は、交差点でList
のNumber
Sと
List
のLong
S。
remove()
(Map
と同様にCollection
)は汎用ではありませんremove()
。これは、任意のタイプのオブジェクトをに渡すことができるためです。削除されるオブジェクトは、渡したオブジェクトと同じタイプである必要はありませんremove()
。それらが等しいことのみが必要です。仕様からremove()
、remove(o)
オブジェクト削除e
など(o==null ? e==null : o.equals(e))
ですtrue
。必要なものは何も存在しないことに注意してくださいo
とe
同じ型です。これは、equals()
メソッドがObject
オブジェクトと同じ型ではなく asパラメータをいます。
Although, it may be commonly true that many classes have equals()
defined so that its objects can only be equal to objects of its own class, that is certainly not always the case. For example, the specification for List.equals()
says that two List objects are equal if they are both Lists and have the same contents, even if they are different implementations of List
. So coming back to the example in this question, it is possible to have a Map<ArrayList, Something>
and for me to call remove()
with a LinkedList
as argument, and it should remove the key which is a list with the same contents. This would not be possible if remove()
were generic and restricted its argument type.
equals()
メソッドも一般化しなかったのかを尋ねるのに役立ちますか?この「リバータリアン」アプローチの代わりに、タイプセーフティの方がより多くの利点を見つけることができました。現在の実装のほとんどのケースは、remove()
メソッドがもたらすこの柔軟性の喜びについてではなく、コードにバグが入り込むためのものだと思います。
equals()
method"?
T
must be declared as a type parameter on the class, and Object
doesn't have any type parameters. There is no way to have a type that refers to "the declaring class".
Equality<T>
with equals(T other)
. Then you could have remove(Equality<T> o)
and o
is just some object that can be compared to another T
.
Because if your type parameter is a wildcard, you can't use a generic remove method.
I seem to recall running into this question with Map's get(Object) method. The get method in this case isn't generic, though it should reasonably expect to be passed an object of the same type as the first type parameter. I realized that if you're passing around Maps with a wildcard as the first type parameter, then there's no way to get an element out of the Map with that method, if that argument was generic. Wildcard arguments can't really be satisfied, because the compiler can't guarantee that the type is correct. I speculate that the reason add is generic is that you're expected to guarantee that the type is correct before adding it to the collection. However, when removing an object, if the type is incorrect then it won't match anything anyway. If the argument were a wildcard the method would simply be unusable, even though you may have an object which you can GUARANTEE belongs to that collection, because you just got a reference to it in the previous line....
うまく説明できなかったかもしれませんが、私には十分に論理的に思えます。
他の答えに加えて、メソッド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
ことです。このようにすると、述語の構築が非常に簡単になります。
yourObject.equals(developer)
:コレクションAPIに記載されているように、java.sun.com/javase/6/docs/api/java/util/...
equals
メソッドのコントラクト、つまり対称性を壊すので、それは乱用です。オブジェクトがequals / hashCode仕様を満たしている限り、removeメソッドはその仕様にのみバインドされるため、どの実装でも他の方法で比較を自由に行うことができます。また、述語オブジェクトは.hashCode()
メソッドを実装していないため(equalsに一貫して実装できないため)、削除呼び出しはHashベースのコレクション(HashSetやHashMap.keys()など)では機能しません。ArrayListで機能することは、幸運です。
Collection.remove
契約を破ることなく完全に実装されます(順序が等しい場合)。そして、equals呼び出しが逆になったさまざまなArrayList(または、AbstractCollection)でも、コントラクトが正しく実装されます- equals
コントラクトを破っているので、意図したとおりに機能しない場合は、それが原因です。
1はのコレクションがあると仮定Cat
し、種類のいくつかのオブジェクト参照をAnimal
、Cat
、SiameseCat
、とDog
。Cat
またはSiameseCat
参照によって参照されているオブジェクトがコレクションに含まれているかどうかをコレクションに尋ねることは合理的であるようです。Animal
参照によって参照されているオブジェクトが含まれているかどうかを尋ねるのはおかしいかもしれませんが、それでも完全に合理的です。問題のオブジェクトは、結局のところ、でありCat
、コレクションに表示される可能性があります。
また、オブジェクトが以外の場合でもCat
、コレクションに表示されるかどうかは問題ありません。単に「いいえ、表示されない」と答えてください。あるタイプの「ルックアップスタイル」のコレクションは、スーパータイプの参照を意味のある形で受け入れ、オブジェクトがコレクション内に存在するかどうかを判断できる必要があります。渡されたオブジェクト参照が無関係の型である場合、コレクションに含まれる可能性がないため、クエリはある意味では意味がありません(常に「いいえ」と答えます)。それでも、パラメータをサブタイプまたはスーパータイプに制限する方法はないため、単純に任意のタイプを受け入れ、コレクションのタイプと関係のないオブジェクトに対しては「いいえ」と答えるのが最も現実的です。
Comparable
がパラメーター化されているためです(比較できるタイプのパラメーター化方法と同様)。次に、人々が無関係なタイプの何かを渡すことを許可することは合理的ではありません。
A
とB
一つのタイプのは、とX
とY
別の、そのようなことA
> B
、およびX
> Y
。A
> Y
とY
< A
、またはX
> B
とB
<のいずれかX
。これらの関係は、マグニチュードの比較が両方のタイプについて知っている場合にのみ存在できます。対照的に、オブジェクトの等値比較メソッドは、問題のある他の型について何も知る必要なく、他の型のどれとも等しくないことを宣言できます。タイプのオブジェクトは、Cat
それがそうであるかどうか
FordMustang
が、それがそのようなオブジェクトと等しいかどうかを判断するのは簡単です(答えは、明らかに「いいえ」です)。
それは妥協でした。どちらのアプローチにも利点があります。
remove(Object o)
remove(E e)
ショートリストから整数を誤って削除しようとするなど、コンパイル時に微妙なバグを検出することで、ほとんどのプログラムが実行したいことにより、タイプセーフが強化されます。Java APIを進化させる場合、下位互換性は常に主要な目標でした。そのため、既存のコードを簡単に生成できるようにするため、remove(Object o)が選択されました。下位互換性が問題でなかった場合、設計者はremove(E e)を選択したと思います。
Removeはジェネリックメソッドではないため、非ジェネリックコレクションを使用する既存のコードはコンパイルされ、同じ動作をします。
詳細については、http://www.ibm.com/developerworks/java/library/j-jtp01255.htmlを参照してください。
編集:コメント作成者が、addメソッドがジェネリックである理由を尋ねます。[...私の説明を削除しました...] 2番目のコメント投稿者は、firebird84からの質問に私よりはるかによく答えました。
もう1つの理由は、インターフェースのためです。これを示す例は次のとおりです。
public interface A {}
public interface B {}
public class MyClass implements A, B {}
public static void main(String[] args) {
Collection<A> collection = new ArrayList<>();
MyClass item = new MyClass();
collection.add(item); // works fine
B b = item; // valid
collection.remove(b); /* It works because the remove method accepts an Object. If it was generic, this would not work */
}
remove()
は共変ではないため、回避できる何かを示しています。しかし問題は、これが許可されるべきかどうかです。ArrayList#remove()
参照の等価性ではなく、値の等価性によって機能します。a B
がa と等しいと予想するのはなぜA
ですか?あなたの例ではそれは可能ですが、それは奇妙な期待です。MyClass
ここであなたが議論を提供することを望みます。
既存の(Java5より前の)コードが壊れるからです。例えば、
Set stringSet = new HashSet();
// do some stuff...
Object o = "foobar";
stringSet.remove(o);
ここで、上記のコードは間違っていると言うかもしれませんが、oが異種のオブジェクトセット(つまり、文字列、数値、オブジェクトなどが含まれている)のセットに由来するとします。すべての一致を削除する必要があります。これは、文字列が等しくないため、文字列を無視すると文字列が無視されるため、合法でした。しかし、それをremove(String o)にすると、機能しなくなります。