Why does this Java 8 program not compile?

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)
21311 次浏览

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());

Type inference was changed in Java 8. Now, type inference looks at both the target type and the parameter types, for both constructors and methods. Consider the following:

interface Iface {}
class Impl implements Iface {}
class Impl2 extends Impl {}


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


<T> T foo(T a) { return a; }

The following is now ok in Java 8 (but not in Java 7):

Acceptor<Impl> a = new Acceptor<>(new Impl2());


// Java 8 cleverly infers Acceptor<Impl>
// While Java 7 infers Acceptor<Impl2> (causing an error)

This, of course, gives an error in both:

Acceptor<Impl> a = new Acceptor<Impl2>(new Impl2());

This also is ok in Java 8:

Acceptor<Impl> a = foo (new Acceptor<>(new Impl2()));


// Java 8 infers Acceptor<Impl> even in this case
// While Java 7, again, infers Acceptor<Impl2>
//   and gives: incompatible types: Acceptor<Impl2> cannot be converted to Acceptor<Impl>

The following gives an error in both, but the error is different:

Acceptor<Impl> a = foo (new Acceptor<Impl2>(new Impl2()));


// Java 7:
// incompatible types: Acceptor<Impl2> cannot be converted to Acceptor<Impl>


// Java 8:
// incompatible types: inferred type does not conform to upper bound(s)
//     inferred: Acceptor<Impl2>
//     upper bound(s): Acceptor<Impl>,java.lang.Object

Clearly, Java 8 made the type inference system smarter. Does this cause incompatibilities? Generally, no. Because of type erasure, it doesn't actually matter what types were inferred, as long as the program compiles. Does Java 8 compile all Java 7 programs? It should, but you brought up a case where it does not.

What seems to be happening is that Java 8 is not handling wildcards well. Instead of considering them as a lack of a constraint, it seems to be treating them as a restrictive constraint that it can't satisfy. I'm not sure if it's following the letter of the JLS, but I'd call this a bug at least in spirit.

FYI, this does work (note that my Acceptor doesn't have the type constraints that yours does):

Acceptor<?> a = new Acceptor<>(new Impl2());

Do note that your example is using a wildcard type outside of a method parameter (which is inadvisable), I wonder if the same issue will happen in more typical code that uses the diamond operator in method calls. (Probably.)

Thanks for the report. This looks like a bug. I will take care of it and probably add a better answer once we have more information about why this is happening. I have filed this bug entry JDK-8043926, to track it.