4

According to the Java API documentation, the Collection<E> has:

  • boolean add(E e)

Similarly Map<K,V> has:

  • V put(K key, V value)

Among these methods, generic types like K, V, E are used as the argument type

Whereas the following methods in Collection<E>

  • boolean contains(Object o)
  • boolean remove(Object o)

and similarly the methods in Map<K,V>:

  • boolean containsKey(Object key)
  • V get(Object key)

have Object as the argument type regardless of the generic types. Moreover, such use of Object as argument type causes ClassCastException now and then.

Question: Why is the Java API designed such that some methods use generic type as the method arguments but some do not use generic types as the method arguments?

0

2 Answers 2

7

Let's look at the documentation for Collection#contains().

boolean contains(Object o)

Returns true if this collection contains the specified element. More formally, returns true if and only if this collection contains at least one element e such that (o==null ? e==null : o.equals(e)).

The equals() method is defined on Object and takes an object as argument. In this design, all objects are comparable with each other, though usually not equal. This also means objects of different types can be considered equal to each other.

So to check whether a collection contains() some element, we don't need an object of that type – we just need an object that implements that kind of equals() that we want. For example, we could abuse that to check whether the collection contains at least one positive integer:

class PositiveIntegerPredicate {
  @Override
  public boolean equals(Object o) {
    if (o == null) return false;
    if (!(o instanceof Integer)) return false;
    return 0 < (Integer)o;
  }
}

A more modern approach would consider expressing that “equality” via a predicate lambda though:

// collection.contains(new PositiveIntegerPredicate())
collection.stream().anyMatch(e -> 0 < i)

So far, we've considered methods like contains() that only compare existing elements. These have a read-only view, so the static type is not so important. This is different when adding elements to a collection. We should only add elements of a suitable type to the collection, thus methods like add() and put() are constrained by type parameters.

1
  • 1
    It's also worth mentioning that in the Java standard library, different types can be legitimately equal to each other. For example, instances of the List interface are equal if the contain equal elements in the same order, even if they are different implementations of List.
    – user102008
    May 7, 2021 at 6:09
0

In addition to the scenario provided by amon, take the following example:

Collection<String> specialStrings = new List<>();
Collection<Integer> specialIntegers = new List<>();

// ... collections are populated
specialStrings.add("special");
specialIntegers.add(3);


boolean isSpecial(Object o)
{
  return specialStrings.contains(o) || specialIntegers.contains(o);
}

This is a trivial/silly example but hopefully it shows the principle. I can pass in any type to this and if it's not a String or Integer, it will just return false.

If contains was restricted to the type of the collection, you would need to add two type checks and two casts. I've seen arguments that forcing the type on these operations would help catch coding errors. There might be something to that but it's not really correct from a typing perspective.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.