7

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

Community
  • 1
  • 1
Hilikus
  • 9,954
  • 14
  • 65
  • 118

7 Answers7

7

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.

Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
  • 3
    According to API 1.4, List contained a method add(Object) that was changed to use generics. So I don't think this is the reason why it wasn't changed. This method signature was maintained on purpose. – FranMowinckel Sep 21 '14 at 19:09
  • @FranMowinckel no, it was maintained because they couldn't change it. restricting add is easy, new code adds the generic type, old code still adds anything. That's precisely why this method couldn't be changed. Because otherwise new code could never detect legacy objects that were injected by old code. – Sean Patrick Floyd Sep 22 '14 at 07:42
  • 1
    What about iterators that use generics then? They would break the code assuming that legacy objects injected all around. I don't think that's the reason because the only methods unchanged are the ones about searching (e.g. contains, indexOf, lastIndexOf...) and remove which needs searching first. It's like they wanted to allow things like overriding the equals methods so you can search by other objects while ensuring that only objects of a certain type where included in the list. – FranMowinckel Sep 22 '14 at 08:06
2

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.

Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
  • 5
    I don't think that's a valid reason. The whole point of generics is to push programmer's errors to compile time. Just like `add`ing a Number to a `List` doesn't make sense and is preferred to see the error at compile time, doing a `contains` with the wrong type should be an error IMO. In short, with your reasoning, there's not point of having generics: just throw an exception or do nothing if you add a Number to a `List` <--> just return false if the types don't match in a `contains` – Hilikus Nov 09 '12 at 16:18
  • BTW, i'm not saying that's not the actual reason why the java guys decided it. Maybe it is, but i'm just challenging the logic in case i'm missing something – Hilikus Nov 09 '12 at 16:34
2

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.

jarnbjo
  • 33,923
  • 7
  • 70
  • 94
  • 2
    As mentioned in one of the linked questions, why would you do that? if you want to interchange the lists then do `List>` But i do see your point, it's just that it smells IMO so it's not a valid argument to justify the design decision – Hilikus Nov 09 '12 at 16:39
  • 1
    It is irrelevant why and if *I* want to do this. The pre-generic contains method was specified this way in the API documentation and introducing generics did not allow the Java developers to change the behaviour or specification of existing methods. – jarnbjo Nov 09 '12 at 16:42
1

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

kosa
  • 65,990
  • 13
  • 130
  • 167
1

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.

Bhesh Gurung
  • 50,430
  • 22
  • 93
  • 142
  • As mentioned in my comment to @dystroy, conceptually, why is it not required to be type-safe but `add` is? if you add a Number to a List you are probably doing something wrong <--> if you check for a Number in a List you are probably doing something wrong – Hilikus Nov 09 '12 at 16:26
  • @Hilikus: That's something which should be taken care of by the `.equals` method of the object itself, which would be to return false. Also the `.equals` methods itself takes an `Object`. `contains` only needs to invoke the `equals` (and `hashCode`) methods, which are available to any object. – Bhesh Gurung Nov 09 '12 at 16:29
0

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
Natix
  • 14,017
  • 7
  • 54
  • 69
  • 1
    The contract of `contains` specifies that it returns `true` if the collections contains an element that is equal the passed object. And because `equals` is defined that it can accept any `Object` as a parameter (in other words, the presence is determined only at runtime by the object's runtime type and values), you couldn't ask if the collection contained an element, even if it actually contained it (as in my code above). Such semantics of `contains` would be quite problematic. On the other hand, I don't see anything unsafe about the current state that allows this. – Natix Nov 09 '12 at 20:08
  • 1
    It would compile fine, you'd just be asked to add a cast to String as you're doing something unsafe. (Unsafe as in: you THINK you're passing the correctly typed object, but instead needed to unwrap it still...) – john16384 Jan 04 '17 at 13:00
0

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.

Yves_T
  • 1,170
  • 2
  • 10
  • 16
  • 1
    What about the method add(Object) that existed in 1.4? This method was changed to use generics, but remove, contains and indexOf were not. So the compile argument is not solid. – FranMowinckel Sep 21 '14 at 19:13