Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Explanation of the Collections.max signature

Tags:

java

generics

I was reading an article on Java Generics when I stumbled on this method signature:

static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll);

The part that I don't get is why we need to have

Collection<? extends T> coll

wouldn't

Collection<T> coll

do as well?

Could someone please explain why the following signature is not adequate?

static <T extends Object & Comparable<? super T>> T max(Collection<T> coll);

Thanks in advance for your replies. This keeps puzzling me for quite some time now..

like image 774
Harry Avatar asked Dec 15 '14 12:12

Harry


People also ask

How does collection max work?

The max() method of java. util. Collections class is used to return the maximum element of the given collection, according to the order induced by the specified comparator. All elements in the collection must be mutually comparable by the specified comparator (that is, comp.


1 Answers

Gábor is correct. The wildcard allows the static type of the returned object to differ from the declared parameter type of the collection you input. For example, given these classes:

interface S extends Comparable<S> {}
class A implements S {
    @Override
    public int compareTo(final @NotNull S o) {
        return 0;
    }
}
class B implements S {
    @Override
    public int compareTo(final @NotNull S o) {
        return 0;
    }
}

And this class:

class Test {

    @Nullable
    static <T extends Comparable<? super T>> T extendsMax(
            Collection<? extends T> coll) {
        return null;
    }

    @Nullable
    static <T extends Comparable<? super T>> T max(Collection<T> coll) {
        return null;
    }
}

Observe what calls compile and what calls do not:

public static void main(String[] args) {
    final Collection<S> sColl = new ArrayList<>();
    final Collection<A> aColl = new ArrayList<>();
    final Collection<B> bColl = new ArrayList<>();

    final S s1 = Test.<S> extendsMax(sColl); // compiles, T = S, <? extends T> = S
    final S s2 = Test.<S> extendsMax(aColl); // compiles, T = S, <? extends T> = A
    final S s3 = Test.<S> extendsMax(bColl); // compiles, T = S, <? extends T> = B
    final A a1 = Test.<A> extendsMax(aColl); // compiles, T = A
    final B b1 = Test.<B> extendsMax(bColl); // compiles, T = B

    final S s4 = Test.<S> max(sColl); // compiles, T = S
    final S s5 = Test.<S> max(aColl); // does not compile, T = S, T != A
    final S s6 = Test.<S> max(bColl); // does not compile, T = S, T != B

    final S s7 = Test.max(aColl); // compiles, but because T = A, and A 
                                  // can be assigned to S
}

So the wildcard allows for some flexibility. While you can omit the wildcard (and to be honest, I can't think of a place off the top of my head where the wildcard is required), there is a reason it is there.


Tom is also incorrect. You can add null to collections with a wildcard (if the collection supports add() in the first place):

List<? extends Number> list = new ArrayList<>();
list.add(null); // compiles, and should execute just fine

And because add(), remove(), and most other mutators in the Collection interface are optional operations, it wouldn't be safe to mutate the collection anyways through those methods if the parameter is just declared as a Collection. In addition, it's generally possible to use iterator().remove() or something of the sort to remove elements from collections regardless of whether they were declared with a wildcard, especially for the ones already included in the Java Collections Framework.

So while a wildcard does limit what you can do with a collection, it should not be used as a way to prevent changes to a collection.

like image 133
awksp Avatar answered Sep 21 '22 17:09

awksp