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.
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.
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.
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.
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.
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));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With