简体   繁体   中英

Compiler error in Java 8, no error in Java 6, why?

I have this code:

import java.util.ArrayList;
import java.util.List;
import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;

class B { }
class C extends B { }
class D { }

class Test {
    void test() {
        List<B> lb = new ArrayList<B>();
        List<C> lc = new ArrayList<C>();
        Iterable<B> it = lb == null ? lc : lb; // a
        FluentIterable.from(lb == null ? lc : lb).transform(new Function<B, D>() { // b
            @Override
            public D apply(B b) {
                return null;
            }
        });
    }
}

Under Java 8 line //b gives me these compiler errors:

Incompatible types. Found: 'java.util.List<C>', required: 'java.util.List<capture<? extends B>>'
Incompatible types. Found: 'java.util.List<B>', required: 'java.util.List<capture<? extends B>>'

Under Java 6 the same line compiles fine.

Line //a

Iterable<B> it = lb == null ? lc : lb;

produces compile error

Incompatible types. Found: 'java.util.List<C>', required: 'java.lang.Iterable<B>'

under both Java 6 and Java 8, which is correct.

But Guava's FluentIterable.from is just a wrapper around Iterable . Why does it not produce any error under Java 6 and does produce errors under Java 8? How does it differ from what I have at line //a ?

Thank you.

TL;DR The language specification for Type Inference was changed in Java 8.

Line //a is a compile time error as a List<C> is not assignable to a variable of type Iterable<B>. If it were then you could compile the following unsound code

List<C> listOfC = new ArrayList<C>();
Iterable<B> listOfB = listOfC; // if this were allowed it would be bad
listOfB.add(new B()); // ...because now my list of C's has a B in it
C reallyAB = listOfC.get(0); // ...and now i have an object of type B in a variable of type C
reallyAB.methodThatsDefinedOnAC(); // ...and crash

Detecting the unsound assignment is straightforward for the compiler, because the variable on line //a has an explict type.

Line //b is more complicated because the method has a generic type parameter, and so the compiler must infer the types for a method at the same time as checking the arguments. The rules surrounding how type parameters are inferred changed significantly with the release of Java 8, which introduced Lambdas into the language, and aimed to improve type inferance in most cases.

https://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html

A detailed analysis of why ternary operators now fail to satisfy generic type constraints was provided here: Generics compilation error with ternary operator in Java 8, but not in Java 7

The incompatibility between the specifications is recorded in this jdk bug: https://bugs.openjdk.java.net/browse/JDK-8044053

You can overcome the limitation in the rules by providing an explicit type. You could assign the expressions result to a variable and pass the variable into the invocation, or you could cast the expression.

    void test() {
        List<B> lb = new ArrayList<B>();
        List<C> lc = new ArrayList<C>();
        FluentIterable.from((List<? extends B>)(lb == null ? lc : lb)).transform(new Function<B, D>() { // b
            @Override
            public D apply(B b) {
                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