Javaコレクションでメソッドが削除されないのはなぜですか?


144

なぜではない Collection.remove(Object o)でジェネリックでですか?

のように思える Collection<E>持っているboolean remove(E o);

次に、誤って(たとえば)Set<String>から個々の文字列を削除しようとするCollection<String>と、後でデバッグの問題が発生する代わりに、コンパイル時エラーになります。


13
オートボクシングと組み合わせると、これは本当にあなたを噛むことができます。Listから何かを削除しようとして、intではなくIntegerを渡すと、remove(Object)メソッドが呼び出されます。
ScArcher2 2008

2
Similar question regarding Map: stackoverflow.com/questions/857420/…
AlikElzin-kilaka

回答:


73

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メソッドなどを一般化しようとしたが、「うまくいかなかった」と述べている。

コレクションのジェネリック型のみをパラメーター型として許可する場合、一般化できない合理的なプログラムが多すぎます。彼が与えられた例は、交差点でListNumberSと ListLongS。


6
Why add() can take a typed parameter but remove() can't is still a bit beyond my comprehension. Josh Bloch would be the definitive reference for Collections questions. It might be all I get without trying to make a similar collection implementation and see for myself. :( Thanks.
Chris Mazzola

2
Chris - read the Java Generics Tutorial PDF, it will explain why.
JeeBee

42
Actually, it's very simple! If add() took a wrong object, it would break the collection. It would contain things it's not supposed to! That is not the case for remove(), or contains().
Kevin Bourrillion

12
ちなみに、この基本的なルール(型パラメーターを使用してコレクションへの実際の損傷のみを防止する)は、ライブラリ全体で完全に一貫して守られています。
Kevin Bourrillion、2009年

3
@KevinBourrillion:私は何年も(JavaとC#の両方で)ジェネリックスを扱ってきましたが、「実際の損傷」のルールが存在することさえ知らなかったのですが... 魚をありがとう!!! 今を除いて、戻って自分の実装を調べて、一部のメソッドが縮退可能であり、したがって縮退する必要があるかどうかを確認する必要があります。はぁ。
corlettk 2013

74

remove()Mapと同様にCollection)は汎用ではありませんremove()。これは、任意のタイプのオブジェクトをに渡すことができるためです。削除されるオブジェクトは、渡したオブジェクトと同じタイプである必要はありませんremove()。それらが等しいことのみが必要です。仕様からremove()remove(o)オブジェクト削除eなど(o==null ? e==null : o.equals(e))ですtrue。必要なものは何も存在しないことに注意してくださいoe同じ型です。これは、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.


1
ただし、Mapを(ArrayListではなく)Map <List、Something>として定義する場合は、LinkedListを使用して削除することができます。この答えは正しくないと思います。
AlikElzin-kilaka 2012

3
答えは正しいようですが、不完全です。それはなぜ一体なぜequals()メソッドも一般化しなかったのかを尋ねるのに役立ちますか?この「リバータリアン」アプローチの代わりに、タイプセーフティの方がより多くの利点を見つけることができました。現在の実装のほとんどのケースは、remove()メソッドがもたらすこの柔軟性の喜びについてではなく、コードにバグが入り込むためのものだと思います。
kellogs

2
@kellogs: What would you mean by "genericize the equals() method"?
newacct

5
@MattBall: "where T is the declaring class" But there is no such syntax in Java. 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".
newacct

3
I think kellogs is saying what if equality were a generic interface 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.
weston

11

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

うまく説明できなかったかもしれませんが、私には十分に論理的に思えます。


1
これについてもう少し詳しく説明してもらえますか?
トーマスオーエンス

6

他の答えに加えて、メソッド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ことです。このようにすると、述語の構築が非常に簡単になります。


投資家?(フィラーフィラーフィラー)
Matt R

3
リストは以下のように実装されているyourObject.equals(developer):コレクションAPIに記載されているように、java.sun.com/javase/6/docs/api/java/util/...
Hosamアリー

13
これは私にとって悪用のようです
RAY

7
述語オブジェクトがequalsメソッドのコントラクト、つまり対称性を壊すので、それは乱用です。オブジェクトがequals / hashCode仕様を満たしている限り、removeメソッドはその仕様にのみバインドされるため、どの実装でも他の方法で比較を自由に行うことができます。また、述語オブジェクトは.hashCode()メソッドを実装していないため(equalsに一貫して実装できないため)、削除呼び出しはHashベースのコレクション(HashSetやHashMap.keys()など)では機能しません。ArrayListで機能することは、幸運です。
–PaŭloEbermann、2011

3
(私は総称型の質問については議論していません-これは以前に回答されました-ここでは述語のイコールの使用のみです。)もちろんHashMapとHashSetはハッシュコードをチェックしており、TreeSet / Mapは要素の順序を使用しています。それでも、Collection.remove契約を破ることなく完全に実装されます(順序が等しい場合)。そして、equals呼び出しが逆になったさまざまなArrayList(または、AbstractCollection)でも、コントラクトが正しく実装されます- equalsコントラクトを破っているので、意図したとおりに機能しない場合は、それが原因です。
–PaŭloEbermann、2011

5

1はのコレクションがあると仮定Catし、種類のいくつかのオブジェクト参照をAnimalCatSiameseCat、とDogCatまたはSiameseCat参照によって参照されているオブジェクトがコレクションに含まれているかどうかをコレクションに尋ねることは合理的であるようです。Animal参照によって参照されているオブジェクトが含まれているかどうかを尋ねるのはおかしいかもしれませんが、それでも完全に合理的です。問題のオブジェクトは、結局のところ、でありCat、コレクションに表示される可能性があります。

また、オブジェクトが以外の場合でもCat、コレクションに表示されるかどうかは問題ありません。単に「いいえ、表示されない」と答えてください。あるタイプの「ルックアップスタイル」のコレクションは、スーパータイプの参照を意味のある形で受け入れ、オブジェクトがコレクション内に存在するかどうかを判断できる必要があります。渡されたオブジェクト参照が無関係の型である場合、コレクションに含まれる可能性がないため、クエリはある意味では意味がありません(常に「いいえ」と答えます)。それでも、パラメータをサブタイプまたはスーパータイプに制限する方法はないため、単純に任意のタイプを受け入れ、コレクションのタイプと関係のないオブジェクトに対しては「いいえ」と答えるのが最も現実的です。


1
「渡されたオブジェクト参照が無関係の型である場合、コレクションに含まれている可能性はありません」間違っています。それと同じものを含んでいればよいだけです。異なるクラスのオブジェクトは同じにすることができます。
newacct 2012年

「怪しげに見えるかもしれませんが、それでも完全に合理的です」ですか?あるタイプのオブジェクトが別のタイプのオブジェクトと等しいかどうかを常にチェックできるとは限らない世界を考えてください。これは、どのタイプと等しいかComparableがパラメーター化されているためです(比較できるタイプのパラメーター化方法と同様)。次に、人々が無関係なタイプの何かを渡すことを許可することは合理的ではありません。
newacct 2012年

@newacct:そこ基本大小比較と等価比較との差である:所与のオブジェクトAB一つのタイプのは、とXY別の、そのようなことA> B、およびX> YA> YY< A、またはX> BB<のいずれかX。これらの関係は、マグニチュードの比較が両方のタイプについて知っている場合にのみ存在できます。対照的に、オブジェクトの等値比較メソッドは、問題のある他の型について何も知る必要なく、他の型のどれとも等しくないことを宣言できます。タイプのオブジェクトは、Catそれがそうであるかどうか
わから

...型のオブジェクトの「より大きい」または「より小さい」FordMustangが、それがそのようなオブジェクトと等しいかどうかを判断するのは簡単です(答えは、明らかに「いいえ」です)。
スーパーキャット2012年

4

これは、remove()がどのタイプのオブジェクトを指定するかを気にする理由がないためだといつも思っていました。それが関係なく、そのオブジェクトがコレクションに含まれているオブジェクトの1つであるかどうかを確認するのは簡単です。何に対してでもequals()を呼び出すことができるからです。add()でタイプをチェックして、そのタイプのオブジェクトのみが含まれていることを確認する必要があります。


0

それは妥協でした。どちらのアプローチにも利点があります。

  • remove(Object o)
    • より柔軟です。たとえば、数値のリストを反復処理し、longのリストから削除することができます。
    • この柔軟性を使用するコードは、より簡単に生成できます
  • remove(E e) ショートリストから整数を誤って削除しようとするなど、コンパイル時に微妙なバグを検出することで、ほとんどのプログラムが実行したいことにより、タイプセーフが強化されます。

Java APIを進化させる場合、下位互換性は常に主要な目標でした。そのため、既存のコードを簡単に生成できるようにするため、remove(Object o)が選択されました。下位互換性が問題でなかった場合、設計者はremove(E e)を選択したと思います。


-1

Removeはジェネリックメソッドではないため、非ジェネリックコレクションを使用する既存のコードはコンパイルされ、同じ動作をします。

詳細については、http://www.ibm.com/developerworks/java/library/j-jtp01255.htmlを参照してください。

編集:コメント作成者が、addメソッドがジェネリックである理由を尋ねます。[...私の説明を削除しました...] 2番目のコメント投稿者は、firebird84からの質問に私よりはるかによく答えました。


2
では、なぜaddメソッドはジェネリックなのでしょうか。
Bob Gettys、

@ firebird84 remove(Object)は間違ったタイプのオブジェクトを無視しますが、remove(E)はコンパイルエラーを引き起こします。それは振る舞いを変えるでしょう。
ノア'19

:shrug:-ランタイムの動作は変更されません。コンパイルエラーは実行時の動作ではありません。addメソッドの「動作」は、このように変更されます。
Jason S、

-2

もう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ここであなたが議論を提供することを望みます。
2015年

-3

既存の(Java5より前の)コードが壊れるからです。例えば、

Set stringSet = new HashSet();
// do some stuff...
Object o = "foobar";
stringSet.remove(o);

ここで、上記のコードは間違っていると言うかもしれませんが、oが異種のオブジェクトセット(つまり、文字列、数値、オブジェクトなどが含まれている)のセットに由来するとします。すべての一致を削除する必要があります。これは、文字列が等しくないため、文字列を無視すると文字列が無視されるため、合法でした。しかし、それをremove(String o)にすると、機能しなくなります。


4
List <String>をインスタンス化する場合、List.remove(someString);のみを呼び出すことができると予想します。下位互換性をサポートする必要がある場合は、生のList-List <?>を使用し、list.remove(someObject)を呼び出すことができますか?
Chris Mazzola、

5
あなたが「削除」を交換する場合は、コードを実際のJava 5で行われたものによって壊れ同じようにあることを「追加」
DJClayworth
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.