Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Practical use of Collections.max() signature

This is a follow up question to Explanation of Collections.max() signature, where the accepted answer does not dive into the practical reason for this wildcard.

The max method takes a Collection<? extends T> and I am unable to think of a practical case where this wildcard helps.

I even referred Oracle More fun with wildcards where it states that

In general, if you have an API that only uses a type parameter T as an argument, its uses should take advantage of lower bounded wildcards (? super T). Conversely, if the API only returns T, you'll give your clients more flexibility by using upper bounded wildcards (? extends T).

But I still didn't get it. Even Java Generics and Collections book does not speak much about the reason behind this wildcard.

Is there a practical use of this? A real world use case would be great.

like image 793
Narendra Pathai Avatar asked May 03 '15 10:05

Narendra Pathai


3 Answers

Imagine these two signatures:

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

Now, let's try to construct functions of type.

Function<Collection<java.sql.Timestamp>, java.util.Date>

// This doesn't work:
Function<Collection<Timestamp>, Date> f0 = 
    (Collection<Timestamp> col) -> Test.<Date>max1(col);

// This works:
Function<Collection<Timestamp>, Date> f1 = 
    (Collection<Timestamp> col) -> Test.<Date>max2(col);

As you can see, using explicit typing, one of the methods doesn't work any longer.

Type inference "hides" this problem in the "real world":

Of course, you could just omit the explicit binding of the generic type argument Test.<Date>maxN():

Function<Collection<Timestamp>, Date> f0 = 
    (Collection<Timestamp> col) -> Test.max1(col);
Function<Collection<Timestamp>, Date> f1 = 
    (Collection<Timestamp> col) -> Test.max2(col);

Or even:

Function<Collection<Timestamp>, Date> f2 = Test::max1;
Function<Collection<Timestamp>, Date> f3 = Test::max2;

And type inference would work its magic, as all of the above compile.

Now why bother?

We should always bother about API consistency, as pbabcdefp has said in his answer. Imagine there was an additional optionalMax() method (just to handle the case of an empty argument collection):

static <T extends ...> Optional<T> optionalMax1(Collection<T> coll);
static <T extends ...> Optional<T> optionalMax2(Collection<? extends T> coll);

Now, you can clearly see that only the second variant is useful:

// Does not work:
Function<Collection<Timestamp>, Optional<Date>> f0 = 
    (Collection<Timestamp> col) -> Test.optionalMax1(col);
Function<Collection<Timestamp>, Optional<Date>> f2 = Test::max1;

// Works:
Function<Collection<Timestamp>, Optional<Date>> f1 = 
    (Collection<Timestamp> col) -> Test.optionalMax2(col);
Function<Collection<Timestamp>, Optional<Date>> f3 = Test::optionalMax2;

With this in mind, the following API would feel very inconsistent, and thus wrong:

static <T extends ...> T max(Collection<T> coll);
static <T extends ...> Optional<T> optionalMax(Collection<? extends T> coll);

So, in order to be consistent (no matter if the typing is needed or not), the methods SHOULD be using the Collection<? extends T> signature. Always.

like image 108
Lukas Eder Avatar answered Oct 10 '22 22:10

Lukas Eder


One useful experiment is this:

static <T extends Object & Comparable<? super T>> T max1(Collection<T> coll) {
    return max2(coll);
}

static <T extends Object & Comparable<? super T>> T max2(Collection<? extends T> coll) {
    return max1(coll);
}

The fact that this compiles shows that the two signatures accept precisely the same set of arguments.

In fact there are several redundant wildcards in the signatures of the Collections class.

For example, the wildcard is not needed in

static <T> void fill(List<? super T> list, T t)

and

static <T> void copy(List<? super T> dst, List<? extends T> src)

would have worked equally well if one or other (but not both) of the wildcards weren't there.

So why include them?

The copy example is a good one. The use of super emphasizes that dst is being used in a write-only sense and the use of extends emphasizes that src is being used in a read-only sense. Similarly in fill, the super, while not needed, emphasizes that the list is being written to, but not read. In max, the extends emphasizes that the List is being read but not written to. For methods where the Collection is both read and written to, such as

static <T extends Comparable<? super T>> void sort(List<T> list)

no wildcard is used.

So in summary, I don't think that there are any practical uses for the wildcard, but it is a good way of indicating the way that the Collection is used by the method.

like image 28
Paul Boddington Avatar answered Oct 10 '22 21:10

Paul Boddington


I believe the practical use for it is more for API designers.

Take Collections.addAll(Collection<? extends E>) as an example. If the class Student and class Teacher would both extend from class Person you could not add a List<Student> or List<Teacher> to a List<Person> to which you would want to send some invitation.

Another practical use could be to prevent modifications adding non-null elements by fault (assume you have no other control over the type of the List from getNumberList()).

In your code you get a reference to a List<Number> and you want to prevent that by fault something is added to this List.

// this would compile
List<Number> workList = getNumberList();
workList.add(new Integer(42));

// this would not compile as ? might be List<Double>
List<? extends Number> workList = getNumberList();
workList.add(new Integer(42));
like image 38
SubOptimal Avatar answered Oct 10 '22 20:10

SubOptimal