I'm trying to figure out why this code does not compile on JDK 1.8.0_45
:
public class Example<E extends Example<E>> {
public List<? extends Example<?>> toExamples(Collection<String> collection) {
return collection.stream()
.map(v -> lookup(v))
.collect(Collectors.toList());
}
public static <E extends Example<E>> E lookup(String value) {
return null;
}
}
Adding a seemingly unnecessary cast fixes it:
public class Example<E extends Example<E>> {
public List<? extends Example<?>> toExamples(Collection<String> collection) {
return collection.stream()
.map(v -> (Example<?>) lookup(v))
.collect(Collectors.toList());
}
public static <E extends Example<E>> E lookup(String value) {
return null;
}
}
Here's the error from the compiler:
Example.java:9: error: incompatible types: inference variable R has incompatible bounds
.collect(Collectors.toList());
^
equality constraints: List<Object>
upper bounds: List<? extends Example<?>>,Object
where R,A,T are type-variables:
R extends Object declared in method <R,A>collect(Collector<? super T,A,R>)
A extends Object declared in method <R,A>collect(Collector<? super T,A,R>)
T extends Object declared in interface Stream
For some reason, the return type of lookup()
isn't correctly inferred to something extending Example
.
Java 8 offers the possibility to create streams out of three primitive types: int, long and double. As Stream<T> is a generic interface, and there is no way to use primitives as a type parameter with generics, three new special interfaces were created: IntStream, LongStream, DoubleStream.
Introduced in Java 8, the Stream API is used to process collections of objects. A stream is a sequence of objects that supports various methods which can be pipelined to produce the desired result. A stream is not a data structure instead it takes input from the Collections, Arrays or I/O channels.
With Java 8, Collection interface has two methods to generate a Stream. stream() − Returns a sequential stream considering collection as its source. parallelStream() − Returns a parallel Stream considering collection as its source.
As Peter Lawrey pointed out, ? extends Example<?>
is not compatible with E extends Example<E>
. Still, even fixing the signature doesn’t make type inference work here.
The reason is a known limitation of the type inference as it does not back-propagate through chained method invocations. In other words, the return type allows to infer the types for the collect(…)
invocation but not for the preceding map(…)
invocation. (see also this answer)
But it works for nested method invocations, so the following rewritten method can be compiled:
public class Example<E extends Example<E>> {
public <E extends Example<E>> List<E> toExamples(Collection<String> collection) {
return collection.stream()
.collect(Collectors.mapping(v -> lookup(v), Collectors.toList()));
}
public static <E extends Example<E>> E lookup(String value) {
return null;
}
}
Still, you have to rethink the semantics of your code. A method’s type parameter which appears only at the return type can’t be correct as it implies that “whatever the caller substitutes for this type parameter, the method will return the right thing”. Since the method implementation doesn’t know what the caller assumes, this is impossible. Only returning null
or an empty list will work correctly, which is of little use.
When you have a ?
it doesn't equal another ?
i.e. the compiler doesn't see
? extends Example<?>
as a match for
E extends Example<E>
as it cannot assume the two ?
are the same. It could be
A extends Example<B>
When you perform the cast, you obscure the constraint so it can match.
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