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 Number
s and a List
of Long
s.
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 i
th 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 i
th 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
Java.Net.Connectexception: Connection Refused
How to Parse Command Line Arguments in Java
Jtextfields on Top of Active Drawing on Jpanel, Threading Problems
How to Convert from Int to String
Why Can't I Do Assignment Outside a Method
Java Runtime.Getruntime(): Getting Output from Executing a Command Line Program
What's the Difference Between @Component, @Repository & @Service Annotations in Spring
Best Way to Format a Double Value to 2 Decimal Places
How to Sanity Check a Date in Java
Dates With No Time or Timezone Component in Java/MySQL
How to Get Rid of Accents and Convert a Whole String to Regular Letters