3

The java.util.List.contains(Object o) method takes Object as an argument and internally uses Object.equals(Object o) as described here.

If I do the following code in Netbeans:

 List<String> listStr = new ArrayList<>();
 listStr.contains(34); //warning

it gives the obvious warning, that is:

Given object can not contain instances of int (expected String)

Since it visible to all, that String never be equal to int then why shouldn't it take Element type E (in my case String) as an argument instead of Object?

Engineer2021
  • 3,288
  • 6
  • 29
  • 51
earthmover
  • 4,395
  • 10
  • 43
  • 74
  • 1
    Alternatively http://stackoverflow.com/questions/857420/what-are-the-reasons-why-map-getobject-key-is-not-fully-generic - same deal with maps – zapl Apr 03 '14 at 12:01
  • @thobens: I do not believe this has to do with generics. – Engineer2021 Apr 03 '14 at 12:57
  • @staticx: Depends on what you think is the question. If you look at the title, this is the exact same issue... – thobens Apr 03 '14 at 13:17
  • @thobens: Afraid not. `int` is boxed up to `Object`. That's not generics... that's just the mechanics of Java. – Engineer2021 Apr 03 '14 at 13:32
  • @staticx: Yeah, obviously. But the question is **why it takes an Object instead of the Element Type `E`**, and thus making it possible to use an `int` (or every other type) instead of a `String`. At least this is how I understand the question. – thobens Apr 03 '14 at 13:39
  • Can OP please clarify? – thobens Apr 03 '14 at 13:43
  • @staticx: have you googled the google talk mentioned in the answer of http://stackoverflow.com/questions/104799/why-arent-java-collections-remove-methods-generic ? – thobens Apr 03 '14 at 13:58
  • 1
    Closely related to http://stackoverflow.com/questions/104799/why-arent-java-collections-remove-methods-generic but arguably not really a duplicate. – Raedwald Apr 03 '14 at 19:34

2 Answers2

1

I think it's because you can do this:

List<?> listStr = new ArrayList<>();

In this case you don't know what is the type, so it would not be possible verify that an element is contained in this list if the type T were take as argument instead of a object. This would break the use of wildcards.

luizcarlosfx
  • 399
  • 1
  • 4
  • 11
1

Strictly speaking such an implementation would be wrong.

The reason for this is that even if an object is not of type E, it could still return true on an equals() call.

Assume for a second, that you've got a class like this:

public class FakeString {
  private final String value;

  public FakeString(String value) {
    if (value == null) {
      throw new IllegalArgumentException();
    }
    this.value = value;
  }

  public int hashCode() {
    return value.hashCode();
  }

  public boolean equals(Object o) {
    return value.equals(o);
  }
}

Then this code would print true:

List<String> strings = Arrays.asList("foo", "bar", "baz");
System.out.println(strings.contains(new FakeString("bar")));

And just to clarify: this behaviour is intended and is the reason why contains() takes an Object instead of E. The same is true for remove(), by the way.


Your int will be boxed to an Object.

(int) === boxing ===> (Integer) ==== reference widening ===> (Object)

But Netbeans is smart and detects this, notices that your Integer is incompatible with a String. Normally this would throw a ClassCastException. However, the javadoc clearly states that throwing the ClassCastException on Collections.contains(Object) is optional.

So in this case, there would be no benefit to overload the contains method to allow an Element type because int, float, double, etc can be autoboxed to Object successfully. Therefore, a contains with just Object is just fine.


For reasons unknown, List, HashSet, Set, and LinkedHashSet do not throw a ClassCastException if "f the type of the specified element is incompatible with this [insert type here] (optional)". Note the optional part. As you have shown, Netbeans will give you a suspicious warning when you try to pass an incompatible type. The trap here is if you try to use logic to rely on the return value of .contains(...). A developer who doesn't pay attention to his warnings could fall into the trap of assuming that contains will always work, but then it doesn't.

As the developer at this blog recommends, here are four steps to avoid this pitfall in the future:

  1. Careful coding and code reviews might lead to developers or reviewers seeing that an object of an irrelevant class is being passed to the contains method. I'm typically uncomfortable leaving it with just this form of protection.

  2. Use of a modern version of NetBeans or of similar tools that flag the "suspicious" behavior can be very helpful.

  3. Unit tests combined with code coverage tests can be helpful in identifying seemingly strange flows through the code that can be attributed to issues such as the one described in this post.

  4. Collection implementations that do throw ClassCastException at least provide a runtime error to let developers know that an improper action is being taken. It may not be as effective as compile-time detection, but it does make it much easier to identify that there is a problem and what it is when it happens then without it. However, the major disadvantage with this approach is that the choice of which collection implementation to use is often driven by important considerations that often outweigh the desire to have runtime detection of an errant call to contains.

Engineer2021
  • 3,288
  • 6
  • 29
  • 51