Why Aren't Java Collections Remove Methods Generic

Why aren't Java Collections remove methods generic?

Josh Bloch and Bill Pugh refer to this issue in Java Puzzlers IV: The
Phantom Reference Menace, Attack of the Clone, and Revenge of The
Shift
.

Josh Bloch says (6:41) that they attempted to generify the get method
of Map, remove method and some other, but "it simply didn't work".

There are too many reasonable programs that could not be generified if
you only allow the generic type of the collection as parameter type.
The example given by him is an intersection of a List of Numbers and a
List of Longs.

Java Collection<T> remove()

Regarding your second question: just make removeInternal non-generic. It should use the same logic that is described in the answer to your first question: removeInternal(o) removes an object e such that (o==null ? e==null : o.equals(e)) is true. (If you implement logic that is not equivalent to this, you are violating the contract for the Collection interface.) There's no need for a generic removeInternal.

EDIT Here's an example of how one might implement a non-generic removeInternal:

private boolean removeInternal(Object o) {
for (int i = 0; i < currentSize; ++i) {
T elt = get(i);
if (o == null ? elt == null : o.equals(elt)) {
removeElementAtPosition(i);
deletedItems.add(elt);
return true;
}
return false;
}
}

This assumes that removeElementAt(int) will correctly transfer the ith item to deletedItems and adjust the internal count of items. It's important to note that the element that was removed is not necessarily the same object as is passed in the argument; it just the first element that satisfies the equality predicate.

Java Collection<T> remove()

Regarding your second question: just make removeInternal non-generic. It should use the same logic that is described in the answer to your first question: removeInternal(o) removes an object e such that (o==null ? e==null : o.equals(e)) is true. (If you implement logic that is not equivalent to this, you are violating the contract for the Collection interface.) There's no need for a generic removeInternal.

EDIT Here's an example of how one might implement a non-generic removeInternal:

private boolean removeInternal(Object o) {
for (int i = 0; i < currentSize; ++i) {
T elt = get(i);
if (o == null ? elt == null : o.equals(elt)) {
removeElementAtPosition(i);
deletedItems.add(elt);
return true;
}
return false;
}
}

This assumes that removeElementAt(int) will correctly transfer the ith item to deletedItems and adjust the internal count of items. It's important to note that the element that was removed is not necessarily the same object as is passed in the argument; it just the first element that satisfies the equality predicate.

Why Map.remove() does not use type parameter?

This is because of backward compatibility with old Java versions (Java 1.4 and older), which did not yet have generics.

In old Java versions, the remove method on Map (as well as other methods) took a parameter of type Object. Sun / Oracle could not change this to remove(T obj) when Java 5 came out, because that would have broken backward compatibility - old source code might not have compiled anymore without changes on the newer Java version.

Oracle is always extremely careful to keep things backward compatible when a new Java version comes out.

Why does Java's TreeSet<E> remove(Object) not take an E

remove(), like get() is required to work when given an equal element (in terms of .equals()). In Java, it is possible (and in some cases, required) for objects of different classes to be equal. Hence, you shouldn't restrict the type.

Why lists' remove() methods take Object and not T?

The issue has to do with backward compatibility of existing code and specification.

What are the reasons why Map.get(Object key) is not (fully) generic

Its all in the details of the Javadoc:

boolean remove(Object o)

Removes a single instance of the specified
element from this collection, if it is present (optional operation).
More formally, removes an element e such that (o==null ? e==null :
o.equals(e)), if this collection contains one or more such elements.
Returns true if this collection contained the specified element (or
equivalently, if this collection changed as a result of the call).

So you see it doesn't matter whether the objects have the same type only that they equal.

Remove item from Collection<? extends MyObject> UnsupportedOperationException

You are probably iterating over an unmodifiable collection, whose iterator will thus not support the remove operation.

Examples of unmodifiable collections include those produced by Collections.unmodifiableXX(). The list returned by Arrays.asList() is not completely unmodifiable (you can set an element at a specific index), but does not support add or remove operations since it is backed directly by the array you pass in.

From the Javadoc for remove():

Throws: UnsupportedOperationException - if the remove operation is not supported by this iterator

You need to look at what you're passing in, and make sure that the structure of the collection can actually be modified. You haven't shown us that code.

Should remove(Object) be remove(? super E)

This is a faulty assumption:

because the operation is bound to fail because of its types, which are known at compile-time

It's the same reasoning that .equals accepts an object: objects don't necessarily need to have the same class in order to be equal. Consider this example with different subtypes of List, as pointed out in the question @Joe linked:

List<ArrayList<?>> arrayLists = new ArrayList<>();
arrayLists.add(new ArrayList<>());

LinkedList<?> emptyLinkedList = new LinkedList<>();
arrayLists.remove(emptyLinkedList); // removes the empty ArrayList and returns true

This would not be possible with the signature you proposed.



Related Topics



Leave a reply



Submit