简体   繁体   中英

Why doesn't this Java 8 stream example compile?

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 .

As Peter Lawrey pointed out , ? extends Example<?> ? 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 ? ie 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.

My guess is that the generic type defined in the static method is not the same as the generic type defined in the class. You should be able to make the lookup method non-static so it matches the same type defined in the class level generic declaration:

    public E lookup(String value) {
        return null;
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM