Why does this Java 8 program not compile?

ghik picture ghik · Apr 14, 2014 · Viewed 20.8k times · Source

This program compiles fine in Java 7 (or in Java 8 with -source 7), but fails to compile with Java 8:

interface Iface<T> {}
class Impl implements Iface<Impl> {}

class Acceptor<T extends Iface<T>> {
    public Acceptor(T obj) {}
}

public class Main {
    public static void main(String[] args) {
        Acceptor<?> acceptor = new Acceptor<>(new Impl());
    }
}

Result:

Main.java:10: error: incompatible types: cannot infer type arguments for Acceptor<>
        Acceptor<?> acceptor = new Acceptor<>(new Impl());
                                           ^
    reason: inference variable T has incompatible bounds
      equality constraints: Impl
      upper bounds: Iface<CAP#1>,Iface<T>
  where T is a type-variable:
    T extends Iface<T> declared in class Acceptor
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Iface<CAP#1> from capture of ?
1 error

In other words, this is a backwards source incompatibility between Java 7 and 8. I've gone through Incompatibilities between Java SE 8 and Java SE 7 list but did not found anything that would fit my problem.

So, is this a bug?

Environment:

$ /usr/lib/jvm/java-8-oracle/bin/java -version
java version "1.8.0"
Java(TM) SE Runtime Environment (build 1.8.0-b132)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)

Answer

nosid picture nosid · Apr 14, 2014

The Java Language Specification changed significantly regarding type inference. In JLS7, type inference was described in §15.12.2.7 and §15.12.2.8, whereas in JLS8, there is a whole chapter dedicated to Chapter 18. Type Inference.

The rules are quite complex, both in JLS7 and JLS8. It's difficult to tell the differences, but obviously there are differences, as is evident from section §18.5.2:

This inference strategy is different than the Java SE 7 Edition of The Java Language Specification [..].

However, the intention of the change was to be backwards compatible. See the last paragraph of section §18.5.2:

[..] The strategy allows for reasonable outcomes in typical use cases, and is backwards compatible with the algorithm in the Java SE 7 Edition of The Java Language Specification.

I can't tell whether that's true or not. However, there are some interesting variations of your code, that do not show the problem. For example, the following statement compiles without errors:

new Acceptor<>(new Impl());

In this case, there is no target type. That means the Class Instance Create Expression is not a poly expressions, and the rules for type inference are simpler. See §18.5.2:

If the invocation is not a poly expression, let the bound set B3 be the same as B2.

That's also the reason, why the following statement works.

Acceptor<?> acceptor = (Acceptor<?>) new Acceptor<>(new Impl());

Although there is a type in the context of the expression, it doesn't count as target type. If a class instance creation expression does not happen in either an assignment expression or an invocation expression, then it can't be a poly expression. See §15.9:

A class instance creation expression is a poly expression (§15.2) if it uses the diamond form for type arguments to the class, and it appears in an assignment context or an invocation context (§5.2, §5.3). Otherwise, it is a standalone expression.

Coming back to your statement. The relevant part of the JLS8 is again §18.5.2. However, I can't tell you if the following statement is correct according to the JLS8, of if the compiler is right with the error message. But at least, you got some alternatives and pointers for further information.

Acceptor<?> acceptor = new Acceptor<>(new Impl());