This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
What are the reasons why Map.get(Object key) is not (fully) generic
Why do we have contains(Object o) instead of contains(E e)?
As you all can see here, a templated java.util.List of type E has its contains method not templated: it takes an Object instead. Does anyone know why?
in what case would a List<String> return true in myList.contains(new OtherNonString())? If I'm not mistaken, never, unless the object that it's compared to has type E as an ancestor (which in my string example is impossible due to String being final)
Is it only to maintain backwards compatibility with pre-generics versions? am I missing a use-case where it makes sense? if it's just for backwards compatibility, why not deprecate contains(Object) and create a contains(E)?
Edit:
Some of my sub-questions had been answered before. For reference, also check this question
if it's just for backwards compatibility, why not deprecate
contains(Object) and create a contains(E)?
Because contains(Object) and contains(E) have the same type erasure (as you can see in this code sample) and hence would cause compilation errors. Also, deprecating methods was not an option, the top priority back then was to make legacy code work.
Because there is no need to have a template here : this would only prevent some tests and if an object isn't of the required class the method would answer false in any case.
It's much simpler to have in your code a simple test checking if the return of the function is a boolean than a test and a try/catch. The few cases where having a type checked at compile time would allow to find a bug aren't worth the overhead.
It's because the method can return true, even if the parameter is of a different type than the list type. More precisely, contains(Object o) will return true if the list contains an element e, so that e.equals(o) is true.
For example, the following code will print true, even if the type of l2 is not allowed in list:
List<ArrayList<String>> list =
new ArrayList<ArrayList<String>>();
ArrayList<String> l1 = new ArrayList<String>();
l1.add("foo");
list.add(l1);
LinkedList<String> l2 = new LinkedList<String>();
l2.add("foo");
System.out.println(list.contains(l2));
The reason for this is that the distinct classes ArrayList and LinkedList both inherit the equals implementation from AbstractList, which does not distinguish between different subclasses. Even if two objects don't have a common superclass, it is possible for their equals implementations to mutually recognize each other.
One of the reason could be contains() doesn't alter list, so don't need to enforce for the type.
From the link you have:
Returns true if this list contains the specified element. More
formally, returns true if and only if this list contains at least one
element e such that (o==null ? e==null : o.equals(e))
Is it only to maintain backwards compatibility with pre-generics versions?
No, that is handled by the type erasure.
It's like that because that method is not required to be type-safe, and doesn't need to return the actual type.
A counter-example:
List<String> strings = Arrays.asList("hello", "world");
Object o = "hello";
System.out.println(strings.contains(o)); // true
If the contains method didn't allow an Object reference as a parameter, It wouldn't be possible to compile the code above. However, the o variable references an instance of a String, which actually is contained in the given list.
The result of contains is determined by the result of Object.equals(Object o) method, which also defines the type of its argument as a general Object, for the very same reason:
String hello = "hello";
Object o = "hello";
System.out.println(hello.equals(o)); // true
Generics in java is implemented with a technique called erasure.
If no generic type is given the type is being replaced with Object.
If necessary the java compiler creates type cast to another object if another generic type is being given.
The compiler also Generate bridge methods to preserve polymorphism in extended generic types.
This is why there are nog generic types during runtime in the compiled bytecode.
for example
public static <T> void printArray ( T [] inputArray ) {
for ( T element : inputArray )
System.out.printf("%s ", element) ;
System.out.println();
}
after erasure is performed by the compiler
public static void printArray ( Object [] inputArray ) {
for ( Object element : inputArray )
System.out.printf("%s ", element) ;
System.out.println();
}
Their is exactly only one copy of this code in memory, which is called for all printArray calls in this example.
The reason why this is done is backwards compatibility. Generics were first introduced in java version 1.5.
In java version < 1.5 you defined a list like this:
List myList = new ArrayList();
and not like this
List<Integer> myList = new ArrayList<Integer>();
To make sure that old code won't break that was already written the compiled class can not contain information about generics.
Related
This question already has answers here:
What are the reasons why Map.get(Object key) is not (fully) generic
(11 answers)
Closed 6 months ago.
Why isn't Collection.remove(Object o) generic?
Seems like Collection<E> could have boolean remove(E o);
Then, when you accidentally try to remove (for example) Set<String> instead of each individual String from a Collection<String>, it would be a compile time error instead of a debugging problem later.
remove() (in Map as well as in Collection) is not generic because you should be able to pass in any type of object to remove(). The object removed does not have to be the same type as the object that you pass in to remove(); it only requires that they be equal. From the specification of remove(), remove(o) removes the object e such that (o==null ? e==null : o.equals(e)) is true. Note that there is nothing requiring o and e to be the same type. This follows from the fact that the equals() method takes in an Object as parameter, not just the same type as the object.
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.
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.
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....
I probably didn't explain it very well, but it seems logical enough to me.
In addition to the other answers, there is another reason why the method should accept an Object, which is predicates. Consider the following sample:
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);
}});
}
}
The point is that the object being passed to the remove method is responsible for defining the equals method. Building predicates becomes very simple this way.
Assume one has a collection of Cat, and some object references of types Animal, Cat, SiameseCat, and Dog. Asking the collection whether it contains the object referred to by the Cat or SiameseCat reference seems reasonable. Asking whether it contains the object referred to by the Animal reference may seem dodgy, but it's still perfectly reasonable. The object in question might, after all, be a Cat, and might appear in the collection.
Further, even if the object happens to be something other than a Cat, there's no problem saying whether it appears in the collection--simply answer "no, it doesn't". A "lookup-style" collection of some type should be able to meaningfully accept reference of any supertype and determine whether the object exists within the collection. If the passed-in object reference is of an unrelated type, there's no way the collection could possibly contain it, so the query is in some sense not meaningful (it will always answer "no"). Nonetheless, since there isn't any way to restrict parameters to being subtypes or supertypes, it's most practical to simply accept any type and answer "no" for any objects whose type is unrelated to that of the collection.
I always figured this was because remove() has no reason to care what type of object you give it. It's easy enough, regardless, to check if that object is one of the ones the Collection contains, since it can call equals() on anything. It's necessary to check type on add() to ensure that it only contains objects of that type.
It was a compromise. Both approaches have their advantage:
remove(Object o)
is more flexible. For example it allows to iterate through a list of numbers and remove them from a list of longs.
code that uses this flexibility can be more easily generified
remove(E e) brings more type safety to what most programs want to do by detecting subtle bugs at compile time, like mistakenly trying to remove an integer from a list of shorts.
Backwards compatibility was always a major goal when evolving the Java API, therefore remove(Object o) was chosen because it made generifying existing code easier. If backwards compatibility had NOT been an issue, I'm guessing the designers would have chosen remove(E e).
Remove is not a generic method so that existing code using a non-generic collection will still compile and still have the same behavior.
See http://www.ibm.com/developerworks/java/library/j-jtp01255.html for details.
Edit: A commenter asks why the add method is generic. [...removed my explanation...] Second commenter answered the question from firebird84 much better than me.
Another reason is because of interfaces. Here is an example to show it :
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 */
}
Because it would break existing (pre-Java5) code. e.g.,
Set stringSet = new HashSet();
// do some stuff...
Object o = "foobar";
stringSet.remove(o);
Now you might say the above code is wrong, but suppose that o came from a heterogeneous set of objects (i.e., it contained strings, number, objects, etc.). You want to remove all the matches, which was legal because remove would just ignore the non-strings because they were non-equal. But if you make it remove(String o), that no longer works.
I'm starting to learn Java and I have a question about generics.
In this methods from Collection<E> interface:
boolean containsAll( Collection <?> c);
boolean removeAll(Collection<?> c);
boolean retainAll ( Collection <?> c);
Why is the parameter Collection <?> c instead of Collection <E> c?
Thanks a lot
The JDK designers wanted code like the following to be possible:
Collection<String> strings = Arrays.asList("foo", "bar", "baz");
Collection<Object> objects = Arrays.asList("foo", 123);
strings.removeAll(objects);
// strigns now contains only "bar" and "baz"
(The above code might not exactly compile because I can't remember how Arrays.asList() captures type parameters, but it should get the point across.)
That is, because you can call .equals() on any pair of objects and get a meaningful result, you don't really need to restrict those methods to a specific item type.
Because a E type parameter needs to be specified while a wildcard ? works for every type. The subtle difference is that
E means any specified type
? means any unknown type
Since there methods are supposed to work on a collection of any unknown type then they doesn't specify a type parameter at all. E is a type variable. ? is not a variable, is a placeholder which cannot be specified.
Is it to maintain backwards compatibility with older (un-genericized) versions of Collection? Or is there a more subtle detail that I am missing? I see this pattern repeated in remove also (remove(Object o)), but add is genericized as add(E e).
contains() takes an Object because the object it matches does not have to be the same type as the object that you pass in to contains(); it only requires that they be equal. From the specification of contains(), contains(o) returns true if there is an object e such that (o==null ? e==null : o.equals(e)) is true. Note that there is nothing requiring o and e to be the same type. This follows from the fact that the equals() method takes in an Object as parameter, not just the same type as the object.
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 Collection<ArrayList> and for me to call contains() with a LinkedList as argument, and it might return true if there is a list with the same contents. This would not be possible if contains() were generic and restricted its argument type to E.
In fact, the fact that contains() takes any object as an argument allows an interesting use where you can to use it to test for the existence of an object in the collection that satisfies a certain property:
Collection<Integer> integers;
boolean oddNumberExists = integers.contains(new Object() {
public boolean equals(Object e) {
Integer i = (Integer)e;
if (i % 2 != 0) return true;
else return false;
}
});
Answered here.
Why aren't Java Collections remove methods generic?
In short, they wanted to maximize backwards compatibility, because collections have been introduced long before generics.
And to add from me: the video he's referring is worth watching.
http://www.youtube.com/watch?v=wDN_EYUvUq0
update
To clarify, the man who said that (in the video) was one of the people who updated java maps and collections to use generics. If he doesn't know, then who.
It is because the contains function utilizes the equals function, and the equals function is defined in the base Object class with a signature of equals(Object o) rather than equals(E e) (since not all classes are generic). Same case with the remove function - it traverses the collection using the equals function which takes an Object argument.
This doesn't directly explain the decision however, as they could've still used type E and allowed it to be automatically cast to type Object on the call to equals; but I imagine they wanted to allow the function to be called on other Object types. There's nothing wrong with having a Collection<Foo> c; and then calling c.contains(somethingOfTypeBar) - it will always return false, and so it eliminates the need for a cast to type Foo (which can throw an exception) or, to protect from the exception, a typeof call. So you can imagine if you're iterating over something with mixed types and calling contains on each of the elements, you can simply use the contains function on all of them rather than needing guards.
It's actually reminiscent of the "newer" loosely-typed languages, when you look at it that way...
Because otherwise it could have only be compared to the exact match of parameter type, specifically wildcarded collections would have stopped working, e.g.
class Base
{
}
class Derived
extends Base
{
}
Collection< ? extends Base > c = ...;
Derived d = ...;
Base base_ref = d;
c.contains( d ); // Would have produced compile error
c.contains( base_ref ); // Would have produced compile error
EDIT
For doubters who think that's not one of the reasons, here is a modified array list with a would be generified contains method
class MyCollection< E > extends ArrayList< E >
{
public boolean myContains( E e )
{
return false;
}
}
MyCollecttion< ? extends Base > c2 = ...;
c2.myContains( d ); // does not compile
c2.myContains( base_ref ); // does not compile
Basically contains( Object o ) is a hack to make this very common use case to work with Java Generics.
"does that basket of apples contain this orange?"
clearly a TRUE answer cannot be given. but that still leaves too possibilities:
the answer is FALSE.
the question is not well formed, it should not pass compile.
the collection api chose the 1st one. but the 2nd choice would also make perfect sense. a question like that is a bullshit question 99.99% of times, so don't even ask!
public void wahey(List<Object> list) {}
wahey(new LinkedList<Number>());
The call to the method will not type-check. I can't even cast the parameter as follows:
wahey((List<Object>) new LinkedList<Number>());
From my research, I have gathered that the reason for not allowing this is type-safety. If we were allowed to do the above, then we could have the following:
List<Double> ld;
wahey(ld);
Inside the method wahey, we could add some Strings to the input list (as the parameter maintains a List<Object> reference). Now, after the method call, ld refers to a list with a type List<Double>, but the actual list contains some String objects!
This seems different to the normal way Java works without generics. For instance:
Object o;
Double d;
String s;
o = s;
d = (Double) o;
What we are doing here is essentially the same thing, except this will pass compile-time checks and only fail at run-time. The version with Lists won't compile.
This leads me to believe this is purely a design decision with regards to the type restrictions on generics. I was hoping to get some comments on this decision?
What you are doing in the "without generics" example is a cast, which makes it clear that you are doing something type-unsafe. The equivalent with generics would be:
Object o;
List<Double> d;
String s;
o = s;
d.add((Double) o);
Which behaves the same way (compiles, but fails at runtime). The reason for not allowing the behavior you're asking about is because it would allow implicit type-unsafe actions, which are much harder to notice in code. For example:
public void Foo(List<Object> list, Object obj) {
list.add(obj);
}
This looks perfectly fine and type-safe until you call it like this:
List<Double> list_d;
String s;
Foo(list_d, s);
Which also looks type-safe, because you as the caller don't necessarily know what Foo is going to do with its parameters.
So in that case you have two seemingly type-safe bits of code, which together end up being type-unsafe. That's bad, because it's hidden and therefore hard to avoid and harder to debug.
Consider if it was...
List<Integer> nums = new ArrayList<Integer>();
List<Object> objs = nums
objs.add("Oh no!");
int x = nums.get(0); //throws ClassCastException
You would be able to add anything of the parent type to the list, which may not be what it was formerly declared as, which as the above example demonstrates, causes all sorts of problems. Thus, it is not allowed.
They aren't subtypes of each other due how generics work. What you want is to declare your function like this:
public void wahey(List<?> list) {}
Then it will accept a List of anything that extends Object. You can also do:
public void wahey(List<? extends Number> list) {}
This will let you take in Lists of something that's a subclass of Number.
I'd recommend you pick up a copy of "Java Generics and Collections" by Maurice Naftalin & Philip Wadler.
There are essentially two dimensions of abstraction here, the list abstraction and the abstraction of its contents. It's perfectly fine to vary along the list abstraction - to say, for instance, that it's a LinkedList or an ArrayList - but it's not fine to further restrict the contents, to say: This (list which holds objects) is a (linked list which holds only numbers). Because any reference that knows it as a (list which holds objects) understands, by the contract of its type, that it can hold any object.
This is quite different from what you have done in the non-generics example code, where you've said: treat this String as if it were a Double. You are instead trying to say: treat this (list which holds only numbers) as a (list which holds anything). And it doesn't, and the compiler can detect it, so it doesn't let you get away with it.
"What we are doing here is essentially
the same thing, except this will pass
compile-time checks and only fail at
run-time. The version with Lists won't
compile."
What you're observing makes perfect sense when you consider that the main purpose of Java generics is to get type incompatibilities to fail at compile time instead of run time.
From java.sun.com
Generics provides a way for you to
communicate the type of a collection
to the compiler, so that it can be
checked. Once the compiler knows the
element type of the collection, the
compiler can check that you have used
the collection consistently and can
insert the correct casts on values
being taken out of the collection.
In Java, List<S> is not a subtype of List<T> when S is a subtype of T. This rule provides type safety.
Let's say we allow a List<String> to be a subtype of List<Object>. Consider the following example:
public void foo(List<Object> objects) {
objects.add(new Integer(42));
}
List<String> strings = new ArrayList<String>();
strings.add("my string");
foo(strings); // this is not allow in java
// now strings has a string and an integer!
// what would happen if we do the following...??
String myString = strings.get(1);
So, forcing this provides type safety but it also has a drawback, it's less flexible. Consider the following example:
class MyCollection<T> {
public void addAll(Collection<T> otherCollection) {
...
}
}
Here you have a collection of T's, you want to add all items from another collection. You can't call this method with a Collection<S> for an S subtype of T. Ideally, this is ok because you are only adding elements into your collection, you are not modifying the parameter collection.
To fix this, Java provides what they call "wildcards". Wildcards are a way of providing covariance/contravariance. Now consider the following using wildcards:
class MyCollection<T> {
// Now we allow all types S that are a subtype of T
public void addAll(Collection<? extends T> otherCollection) {
...
otherCollection.add(new S()); // ERROR! not allowed (Here S is a subtype of T)
}
}
Now, with wildcards we allow covariance in the type T and we block operations that are not type safe (for example adding an item into the collection). This way we get flexibility and type safety.
This question already has answers here:
What are the reasons why Map.get(Object key) is not (fully) generic
(11 answers)
Closed 6 months ago.
Why isn't Collection.remove(Object o) generic?
Seems like Collection<E> could have boolean remove(E o);
Then, when you accidentally try to remove (for example) Set<String> instead of each individual String from a Collection<String>, it would be a compile time error instead of a debugging problem later.
remove() (in Map as well as in Collection) is not generic because you should be able to pass in any type of object to remove(). The object removed does not have to be the same type as the object that you pass in to remove(); it only requires that they be equal. From the specification of remove(), remove(o) removes the object e such that (o==null ? e==null : o.equals(e)) is true. Note that there is nothing requiring o and e to be the same type. This follows from the fact that the equals() method takes in an Object as parameter, not just the same type as the object.
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.
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.
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....
I probably didn't explain it very well, but it seems logical enough to me.
In addition to the other answers, there is another reason why the method should accept an Object, which is predicates. Consider the following sample:
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);
}});
}
}
The point is that the object being passed to the remove method is responsible for defining the equals method. Building predicates becomes very simple this way.
Assume one has a collection of Cat, and some object references of types Animal, Cat, SiameseCat, and Dog. Asking the collection whether it contains the object referred to by the Cat or SiameseCat reference seems reasonable. Asking whether it contains the object referred to by the Animal reference may seem dodgy, but it's still perfectly reasonable. The object in question might, after all, be a Cat, and might appear in the collection.
Further, even if the object happens to be something other than a Cat, there's no problem saying whether it appears in the collection--simply answer "no, it doesn't". A "lookup-style" collection of some type should be able to meaningfully accept reference of any supertype and determine whether the object exists within the collection. If the passed-in object reference is of an unrelated type, there's no way the collection could possibly contain it, so the query is in some sense not meaningful (it will always answer "no"). Nonetheless, since there isn't any way to restrict parameters to being subtypes or supertypes, it's most practical to simply accept any type and answer "no" for any objects whose type is unrelated to that of the collection.
I always figured this was because remove() has no reason to care what type of object you give it. It's easy enough, regardless, to check if that object is one of the ones the Collection contains, since it can call equals() on anything. It's necessary to check type on add() to ensure that it only contains objects of that type.
It was a compromise. Both approaches have their advantage:
remove(Object o)
is more flexible. For example it allows to iterate through a list of numbers and remove them from a list of longs.
code that uses this flexibility can be more easily generified
remove(E e) brings more type safety to what most programs want to do by detecting subtle bugs at compile time, like mistakenly trying to remove an integer from a list of shorts.
Backwards compatibility was always a major goal when evolving the Java API, therefore remove(Object o) was chosen because it made generifying existing code easier. If backwards compatibility had NOT been an issue, I'm guessing the designers would have chosen remove(E e).
Remove is not a generic method so that existing code using a non-generic collection will still compile and still have the same behavior.
See http://www.ibm.com/developerworks/java/library/j-jtp01255.html for details.
Edit: A commenter asks why the add method is generic. [...removed my explanation...] Second commenter answered the question from firebird84 much better than me.
Another reason is because of interfaces. Here is an example to show it :
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 */
}
Because it would break existing (pre-Java5) code. e.g.,
Set stringSet = new HashSet();
// do some stuff...
Object o = "foobar";
stringSet.remove(o);
Now you might say the above code is wrong, but suppose that o came from a heterogeneous set of objects (i.e., it contained strings, number, objects, etc.). You want to remove all the matches, which was legal because remove would just ignore the non-strings because they were non-equal. But if you make it remove(String o), that no longer works.