Why do we have contains(Object o) instead of contains(E e)?
Asked Answered
F

6

22

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

Fleurette answered 8/6, 2010 at 0:43 Comment(1)
possible duplicate of Why aren't Java Collections remove methods generic?Bogle
B
16

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;
    }
});
Bogle answered 8/6, 2010 at 2:5 Comment(2)
Also this method signature contains( Object o ) provides a really nice gun to conveniently shoot yourself in a foot. This method is really a showcase of limitation of Java Generics.Castled
"This would not be possible if contains() were generic and restricted its argument type to E." - yes, but it's still possible to achieve the same objective if contains had an overload that accepted a Comparator<E> - which could then be used in the way you suggest (to perform equality comparisons with arbitrary predicates and conditions) and do it in a type-safer manner.Charkha
E
5

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.

Epiphenomenalism answered 8/6, 2010 at 1:26 Comment(0)
S
4

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

Saraband answered 8/6, 2010 at 0:49 Comment(2)
This has nothing to do with the real reason. It would be much nicer to have some level of type checking on contains and maybe remove. It would have been much less error prone.Castled
"it will always return false" no it won't. it is perfectly possible for one class to .equals an object of another classBogle
C
0

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.

Castled answered 8/6, 2010 at 1:27 Comment(2)
Under normal scenario, no one would want to cast a collection to Collection<? extends Foo> during element manipulation. If this is the case, add(E e) method would have been a failure as well.Swampland
@Jai. Here is a scenario void myMethod( Collection<? extends Foo> coll ). If inside you need to see if some verison of Foo is contained in this colleciton, you would hit the limitation described in this answer.Castled
G
0

"does that basket of apples contain this orange?"

clearly a TRUE answer cannot be given. but that still leaves too possibilities:

  1. the answer is FALSE.
  2. 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!

Gaussmeter answered 8/6, 2010 at 20:59 Comment(6)
The question should not be considered ill-formed, if one e.g. has two baskets that contain fruits, and wants to know if any fruit in one basket matches a fruit in the other. If one were to know that one basket only contained apples and one only contained oranges, it may make sense to short-circuit the search for a match, but suppose one basket was known to contain only apples and the other contained mixed fruits. Asking the apple basket about each fruit in the other basket without validating its type first would be easier than checking each fruit before asking to see if it's an apple.Smail
in my example, the parameter is known to be an orange, therefore the question sounds ridiculous. in your example, the question would be "does that basket of apples contain this fruit?", that is legit. the use case is rarer though. We can redesign the API to allow your question while forbidding my question, by restricting the parameter type to be super types of Apple.Gaussmeter
There is no mechanism in .Net via which a method can restrict a parameter, generic or otherwise, to be a supertype of some other type, and I don't think Java has such a mechanism either. Such a thing would tend to violate the Liskov Substitution Principle, since a method which is usable with Fruit should be usable with Orange. There are ways in .Net via which one could use Obsolete tags to trigger compiler squawks in some particular statically-identifiable silly cases, but I would think that would be more confusing than helpful.Smail
in java you can declare a type variable with lower bound of E. this doesn't violate the liskov thingy, because generic methods are overloading methods.Gaussmeter
Given an abstract class Bird and an interface Flyable (which is not implemented by Bird, since not all birds can fly), a collection of Bird, and a reference to a Flyable, should one by allowed to look for that Flyable within the collection? Note that Flyable is neither a subtype nor supertype of Bird; the referred-to instance could be a Sparrow (which is bird) or a Dragonfly (which is not). Would there any way to allow that while compile-time squawking at an attempt to search for a PersianCat within a collection of Bird?Smail
I'll put that in the 0.01%.Gaussmeter
T
0

Java collections generic classes make a reasonable assumption that any method that takes parameter of type <E> as input is considered as mutable method. And the call to such methods is prohibited within the implementation of generic methods. But contains() method needs to be treated as immutable method and its call should be made possible within the implementation of generic methods. This is why contains() method accepts Object as input and not type <E> as input.

Titlark answered 10/4 at 5:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.