Reputation: 1216
I'm trying to learn concepts of generic programming in Java, but I stuck on one question.Consider these implementations of function max:
1)
public static <T extends Comparable<? super T>> T max(Collection<? extends T> collection){
//code
}
2)
public static <T extends Comparable<T>> T max(Collection<T> collection){
//code
}
I'm curious to know, what is the difference between them?
Of course, I know, that declaration Collection<? extends T>
allows to pass subtypes of T as Collection, but what is the use of it in this case, in static method?
The result has to be the same without using bounds, hasn't it?
And what about this <T extends Comparable<? super T>>
?
If you pass subtype of T, it will be okay anyway, because T implements Comparable and implementation of Comparable will maintain in subtype, so you can use 2) option.
I'm just wondering whether there are some cases, when it's impossible to use 2) option, but possible to use 1)
Upvotes: 2
Views: 408
Reputation: 159086
To fully understand it, you're missing two versions. Here are all four:
public static <T extends Comparable<? super T>> T max1(Collection<? extends T> collection){
throw new UnsupportedOperationException("Not yet implemented");
}
public static <T extends Comparable<T>> T max2(Collection<T> collection){
throw new UnsupportedOperationException("Not yet implemented");
}
public static <T extends Comparable<T>> T max3(Collection<? extends T> collection){
throw new UnsupportedOperationException("Not yet implemented");
}
public static <T extends Comparable<? super T>> T max4(Collection<T> collection){
throw new UnsupportedOperationException("Not yet implemented");
}
To see how they work, I'll steal the example by @MattTimmermans:
class A implements Comparable<A> {
@Override public int compareTo(A that) {
throw new UnsupportedOperationException("Not yet implemented");
}
}
class B extends A {
}
Now let's see which of the four max methods work:
List<B> list = new ArrayList<>();
B maxItem1 = max1(list); // T = B (but could be A if result is A, see next line)
A maxItem1a = Test.<A>max1(list); // T = A
B maxItem1b = Test.<B>max1(list); // T = B
B maxItem2 = max2(list); // compile error
A maxItem3 = max3(list); // T = A (forced by Comparable<T>)
B maxItem4 = max4(list); // T = B (forced by Collection<T>)
As you can see, for max3()
and max4()
, T
is being forced to a specific type by the lack of a wildcard.
max2()
doesn't work at all, because there is no T
that can satisfy both Comparable<T>
and Collection<T>
.
As for max1()
, T
can be resolved to either A
or B
, but if it is resolved to A
, then assignment must be to a variable of type A
. If resolved to B
, assignment can be to either A
or B
.
If you don't manually specify a type for T
, the compiler will choose B
.
Note that since your max method will likely just iterate the collection once, you can even change parameter type to Iterable
, and everything will still work correctly.
This will allow you to find max value for non-collection iterables, such as java.nio.file.Path
to find the last file (lexicographically) in a folder (if that makes sense).
Upvotes: 1
Reputation: 48804
Lets break this down into parts:
<T extends Comparable<? super T>>
This defines the generic bounds of T
- it can represent any type that is Comparable
- either because T
is Comparable
or because some parent class is. Since that parent class (call it P
) likely implements Comparable<P>
rather than Comparable<T>
it's necessary to use super
here. It sounds like you know this already.
T
This is the return type of the method, and can often be specified by the calling context (for instance when you assign the result to a variable). We'll come back to this.
Collection<? extends T>
This allows you to pass in a Collection
of T
s or any subtype of T
(call it S
). If you just specified Collection<T>
you'd only be able to pass Collection<T>
objects and not Collection<S>
objects.
Putting them all together this lets callers do something like:
// notice that max() returns a C, but is assignable to a B
B b = max(new ArrayList<C>());
Where A
, B
, and C
are:
A implements Comparable<A>
B extends A
C extends B
It turns out, since your method returns a T
directly, that the Collection<? extends T>
doesn't really matter because you can always assign a child type (C
) to a parent type (B
). But if we tweak the method signature slightly to return a type using T
as a generic this doesn't work:
public static <T extends Comparable<? super T>> Collection<T>
collect(Collection<T> collection)
Notice we're now returning a Collection<T>
- now the ? extends
becomes important - this doesn't compile:
Collection<B> b = collect(new ArrayList<C>());
Whereas if you change the parameter to Collection<? extends T>
you loosen the bounds; the return type doesn't have to be the same as the parameter type. You can pass in a Collection<C>
but because the caller makes T
mean B
(since that's the type being expected as the return type) C
now extends T
, rather than is T
.
So in short, for your method I don't believe you need the ? extends T
, but in some cases you do. More generally if you intend to support subtypes in your generics it's a good idea to do so explicitly, even if it's not strictly necessary.
Upvotes: 1
Reputation: 59154
This works with the first version, but not the second:
class A implements Comparable<A> {...};
class B extends A {...};
List<B> list = new ArrayList<>();
B maxItem = max(list);
In both cases, the type parameter T
is set to B
because list
is a Collection<B>
. B
doesn't implement Comparable<B>
, however, so it can't satisfy the constraint on T extends Comparable<T>
in the second declaration.
It works with the first declaration, though, since A
is a superclass of B
.
That explains the Comparable<? super T>
... The Collection<? extends T>
is not really necessary here -- I don't think there is any reason for the max method to return a type other than the collection element type T, so Collection<T>
would be fine.
Upvotes: 2