6

Possible Duplicate:
What are the reasons why Map.get(Object key) is not (fully) generic
Why does Java's TreeSet<E> remove(Object) not take an E

Why does HashSet not restrict type of argument to E here:

public boolean contains(Object o)
public boolean remove(Object o)

like it does for add()

public boolean add(E e)

I mean if the compiler is enforcing that only objects of type E are being added, then the set can't contain/remove any other type

Community
  • 1
  • 1
user674669
  • 10,681
  • 15
  • 72
  • 105

4 Answers4

4

The difference is that adding must be type-safe to preserve the integrity of the collection, while item checking/removal can afford to be "type-forgiving" without the risk of harming type safety of the collection. In other words, if you add an element of a wrong type, the set will become invalid; on the other hand, if you check for a presence of an element of a wrong type, you'll simply get back a false. Same goes for remove: if you pass an element of an incompatible type, it's not going to be in the set +, so the removal is going to be a no-op.


+ Unless you put it in through a hack that exploits type erasure.
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 1
    If you pass an element of an incompatible type, the element _may still be_ in the set - see my answer. – npe Oct 09 '12 at 20:31
  • 1
    @npe Yes, the good old type erasure hack! I updated the answer to mention the possibility, though the core of the answer remains the same: the distinction between generic and non-generic methods of the interface has been done along the harmful/harmless lines. – Sergey Kalinichenko Oct 09 '12 at 20:42
  • + Unless you put it in through a hack that exploits type erasure or it simply equals an element in the set... (see my answer for details). – DaveFar Oct 09 '12 at 21:14
2

then the set can't contain/remove any other type

Of course it can. Read about type erasure or cast your HashSet<E> to non-generic HashSet and add an object which is not of type E to it.

Check out this code:

Integer testInt = new Integer(3);

// First, create a generic set of strings
HashSet<String> set = new HashSet<String>();
set.add("abc");

// Then make it non-generic and add an integer to it
((HashSet) set).add(testInt);

// Now your set-of-strings contains an integer!
System.out.println(set); // prints: [abc, 3]

// Remove the integer
set.remove(testInt);
System.out.println(set); // prints: [abc]

The reason of this weirdness is that the information of generic types is erased in runtime and your set becomes a simple set of objects.

npe
  • 15,395
  • 1
  • 56
  • 55
  • `HashSet>` can you add element with that? – Bhesh Gurung Oct 09 '12 at 20:20
  • Nope, you cannot. I'll correct the answer. – npe Oct 09 '12 at 20:23
  • When you use the ? wildcard as a generic parameter, you are telling the compiler that you have an instance, and generic parameter is some value, but you don't know what. Therefore, it's inherently unsafe to call any method on that object which uses that generic parameter. Take HashSet as an example. you could construct HashSet but assign it to HashSet> how would the compiler know what type the parameter really is? Answer: it can't. The same is true even if the wildcard is bounded (ie. HashSet extends Number>) – Matt Oct 09 '12 at 21:01
2

The parameter of contains and remove cannot be restricted to E because you should be able to give them just equal objects, which is quite useful. More precisely, the API of HashSet.remove says:

... More formally, removes an element e such that (o==null ? e==null : o.equals(e)), if this set contains such an element.

Object.equals takes Object as parameter, which is also quite useful to enable equality between different types.

Thus, to enable the more general functionality of contains and remove (on equivalence classes instead of only object identity), they have to take Object as parameter.


Example:

    HashSet<ArrayList<String>> set = new HashSet<ArrayList<String>>();
    ArrayList<String> list = new ArrayList<String>();
    list.add("foo");
    LinkedList<String> equalList = new LinkedList<String>();
    equalList.add("foo");
    set.add(list);

    System.out.println(list.equals(equalList)); // prints: true
    System.out.println(set.contains(equalList)); // prints: true

    System.out.println(set); // prints: [[foo]]
    set.remove(equalList);
    System.out.println(set); // prints: [[]]
DaveFar
  • 7,078
  • 4
  • 50
  • 90
0

You're not adding anything to the set with those two methods, so the type parameter doesn't need to be constrained. If the type doesn't match, the methods can just return false.

Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880