This class compiles ok in Java 7, but not in Java 8:
这个类在Java 7中编译好,但在Java 8中没编译:
public class Foo {
public static void main(String[] args) throws Exception {
//compiles fine in Java 7 and Java 8:
Class<? extends CharSequence> aClass = true ? String.class : StringBuilder.class;
CharSequence foo = foo(aClass);
//Inlining the variable, compiles in Java 7, but not in Java 8:
CharSequence foo2 = foo(true ? String.class : StringBuilder.class);
}
static <T> T foo(Class<T> clazz) throws Exception {
return clazz.newInstance();
}
}
Compilation error:
Error:(9, 29) java: method foo in class Foo cannot be applied to given types; required: java.lang.Class found: true ? Str[...]class
reason: inferred type does not conform to equality constraint(s) inferred: java.lang.StringBuilder equality constraints(s): java.lang.StringBuilder,java.lang.String错误:(9,29)java:类Foo中的方法foo不能应用于给定的类型; required:java.lang.Class发现:true? Str [...]类原因:推断类型不符合推断的等式约束:java.lang.StringBuilder等式约束:java.lang.StringBuilder,java.lang.String
Why has this stopped working in Java 8? Is it intentional / a side effect of some other feature, or is it simply a compiler bug?
为什么这在Java 8中停止工作?是故意/某些其他功能的副作用,还是仅仅是编译器错误?
3 个解决方案
#1
11
This is not a javac bug, according to the current spec. I wrote an answer here is SO for a similar issue. Here the problem is more or less the same.
根据目前的规范,这不是一个javac bug。我在这里写的答案是针对类似问题的。这里的问题或多或少都是一样的。
On an assignment or invocation context reference conditional expressions are poly expressions. This means that the type of the expression is not the result of applying capture conversion to lub(T1, T2), see JSL-15.25.3 for a detailed definition of T1 and T2. Instead we have, also from this portion of the spec that:
在赋值或调用上下文引用中,条件表达式是多义表达式。这意味着表达式的类型不是将捕获转换应用于lub(T1,T2)的结果,有关T1和T2的详细定义,请参阅JSL-15.25.3。相反,我们也从规范的这一部分得到:
Where a poly reference conditional expression appears in a context of a particular kind with target type T, its second and third operand expressions similarly appear in a context of the same kind with target type T.
当poly引用条件表达式出现在具有目标类型T的特定类型的上下文中时,其第二和第三操作数表达式类似地出现在具有目标类型T的相同类型的上下文中。
The type of a poly reference conditional expression is the same as its target type.
多参考条件表达式的类型与其目标类型相同。
So this means that the target type is pushed down to both operands of the reference conditional expression, and both operands are attributed against that target type. So the compiler ends up gathering constraints from both operands, leading to an unsolvable constraint set and thus an error.
因此,这意味着目标类型被下推到引用条件表达式的两个操作数,并且两个操作数都归因于该目标类型。因此编译器最终从两个操作数收集约束,导致无法解决的约束集,从而导致错误。
OK, but why do we get equality bounds for T here?
好的,但为什么我们在这里得到T的相等边界?
Let's see in detail, from the call:
让我们从电话中详细了解:
foo(true ? String.class : StringBuilder.class)
where foo is:
foo在哪里:
static <T> T foo(Class<T> clazz) throws Exception {
return clazz.newInstance();
}
We have that as we are invoking method foo()
with the expression true ? String.class : StringBuilder.class
. This reference conditional expression should be compatible in a loose invocation context with type Class<T>
. This is represented as, see JLS-18.1.2:
我们有,因为我们用表达式true调用方法foo()? String.class:StringBuilder.class。此引用条件表达式应在类型为Class
true ? String.class : StringBuilder.class → Class<T>
As follows from JLS-18.2.1 we have that:
如下JLS-18.2.1我们有:
A constraint formula of the form ‹Expression → T› is reduced as follows:
形式的约束公式如下: →t>...
- If the expression is a conditional expression of the form e1 ? e2 : e3, the constraint reduces to two constraint formulas, ‹e2 → T› and ‹e3 → T›.
如果表达式是e1形式的条件表达式? e2:e3,约束减少为两个约束公式,
和 →t>。 →t>
This implies that we obtain the following constraint formulas:
这意味着我们获得了以下约束公式:
String.class → Class<T>
StringBuilder.class → Class<T>
or:
Class<String> → Class<T>
Class<StringBuilder> → Class<T>
Later from JLS-18.2.2 we have that:
后来从JLS-18.2.2我们得到:
A constraint formula of the form ‹S → T› is reduced as follows:
形式
的约束公式减少如下:→t>...
- Otherwise, the constraint reduces to ‹S <: T›.
否则,约束减少到
。
I'm only including the related parts. So going on we have now:
我只包括相关部分。所以继续我们现在:
Class<String> <: Class<T>
Class<StringBuilder> <: Class<T>
From JLS-18.2.3, we have:
从JLS-18.2.3开始,我们有:
A constraint formula of the form ‹S <: T› is reduced as follows:
形式
的约束公式减少如下:...
- Otherwise, the constraint is reduced according to the form of T:
- If T is a parameterized class or interface type, or an inner class type of a parameterized class or interface type (directly or indirectly), let A1, ..., An be the type arguments of T. Among the supertypes of S, a corresponding class or interface type is identified, with type arguments B1, ..., Bn. If no such type exists, the constraint reduces to false. Otherwise, the constraint reduces to the following new constraints: for all i (1 ≤ i ≤ n), ‹Bi <= Ai›.
如果T是参数化类或接口类型,或参数化类或接口类型(直接或间接)的内部类类型,则让A1,...,An成为T的类型参数。在S的超类型中,a识别相应的类或接口类型,类型参数为B1,...,Bn。如果不存在此类型,则约束将减少为false。否则,约束减少到以下新约束:对于所有i(1≤i≤n),
。 否则,根据T的形式减少约束:如果T是参数化类或接口类型,或参数化类或接口类型的内部类类型(直接或间接),则让A1,...,An为T的类型参数在S的超类型中,识别相应的类或接口类型,类型参数为B1,...,Bn。如果不存在此类型,则约束将减少为false。否则,约束减少到以下新约束:对于所有i(1≤i≤n),
。
So as Class<T>
, Class<String>
and Class<StringBuilder>
are parameterized classes, this implies that now our constraints reduces to:
因此Class
String <= T
StringBuilder <= T
Also from JLS-18.2.3, we have:
同样来自JLS-18.2.3,我们有:
A constraint formula of the form ‹S <= T›, where S and T are type arguments (§4.5.1), is reduced as follows:
形式
的约束公式,其中S和T是类型参数(第4.5.1节),如下所示:...
- If T is a type:
- If S is a type, the constraint reduces to ‹S = T›.
如果S是类型,则约束减少到
。如果T是类型:如果S是类型,则约束减少到
。
Thus we end up with these constraints for T:
因此我们最终得到了T的这些约束:
String = T
StringBuilder = T
Finally at JLS-18.2.4 we have that:
最后在JLS-18.2.4我们有:
A constraint formula of the form ‹S = T›, where S and T are types, is reduced as follows:
形式
的约束公式,其中S和T是类型,减少如下:...
- Otherwise, if T is an inference variable, α, the constraint reduces to the bound S = α.
否则,如果T是推理变量α,则约束减小到边界S =α。
And there is no solution for type variable T with bounds T = String
and T = StringBuilder
. There is no type the compiler can substitute T for that satisfies both restrictions. For this reason the compiler displays the error message.
对于带有边界T = String和T = StringBuilder的类型变量T,没有解决方案。编译器没有可以替代T的类型满足两个限制。因此,编译器显示错误消息。
So javac is OK according to the current spec, but is the spec correct on this? Well there is a compatibility issue between 7 and 8 that should be investigated. For this reason I have filed JDK-8044053 so we can track this issue.
所以根据当前的规范,javac是可以的,但这个规格是正确的吗?那么应该调查7到8之间的兼容性问题。出于这个原因,我已经提交了JDK-8044053,因此我们可以跟踪此问题。
#2
17
I'm going to go out on a limb and say that this error (while it may or may not conform to the updated JLS, which I admit I haven't read in detail) is due to an inconsistency in type handling by the JDK 8 compiler.
我打算说出这个错误(虽然它可能符合或不符合更新的JLS,我承认我没有详细阅读)是由于JDK的类型处理不一致8编译器。
In general the ternary operator used the same type inference as if for a two-argument method which had the formal parameters both based on the same type parameter. For instance:
通常,三元运算符使用相同的类型推断,就像对于具有基于相同类型参数的形式参数的双参数方法一样。例如:
static <T> T foo(Class<? extends T> clazz, Class<? extends T> clazz2) { return null; }
public static void main(String[] args) {
CharSequence foo2 = foo(String.class, StringBuilder.class);
}
In this example, T
can be inferred to be a capture of ? extends Object & Serializable & CharSequence
. Now similarly, in JDK 7, if we go back to your original example:
在这个例子中,T可以推断为捕获? extends Object&Serializable&CharSequence。现在同样,在JDK 7中,如果我们回到原始示例:
CharSequence foo2 = foo(true ? String.class : StringBuilder.class);
This does almost the exact same type inference as above, but in this case consider the ternary operator to be a method as such:
这几乎与上面完全相同的类型推断,但在这种情况下,将三元运算符视为一种方法:
static <T> T ternary(boolean cond, T a, T b) {
if (cond) return a;
else return b;
}
So in this case, if you pass String.class and StringBuilder.class as the parameters, the inferred type of T is (roughly speaking) Class<? extends Object & Serializable & CharSequence>
, which is what we wanted.
所以在这种情况下,如果你传递String.class和StringBuilder.class作为参数,推断的T类型(粗略地说)是Class ,这就是我们想要的。
In fact you can replace the use of your ternary operator in the original snippet with this method, thus:
实际上,您可以使用此方法替换原始代码段中三元运算符的使用,因此:
public class Foo {
public static void main(String[] args) throws Exception {
//compiles fine in Java 7 and Java 8:
Class<? extends CharSequence> aClass = true ? String.class : StringBuilder.class;
CharSequence foo = foo(aClass);
//Inlining the variable using 'ternary' method:
CharSequence foo2 = foo(ternary(true, String.class, StringBuilder.class));
}
static <T> T foo(Class<T> clazz) throws Exception {
return clazz.newInstance();
}
static <T> T ternary(boolean cond, T a, T b) {
if (cond) return a;
else return b;
}
}
... And now it compiles in Java 7 and 8 (edit: actually it also fails with Java 8! edit again: it now works, Jdk 8u20). What gives? for some reason an equality constraint is now being imposed on T (in the foo method), rather than a lower-bounds constraint. The relevant section of the JLS for Java 7 is 15.12.2.7; for Java 8 there's a whole new chapter on type inference (chapter 18).
...现在它在Java 7和8中编译(编辑:实际上它也失败了Java 8!再次编辑:它现在可以工作,Jdk 8u20)。是什么赋予了?由于某种原因,现在对T(在foo方法中)施加了等式约束,而不是下限约束。 JLS for Java 7的相关部分是15.12.2.7;对于Java 8,有一个关于类型推断的全新章节(第18章)。
Note that explicitly typing T in the call to 'ternary' does allow compilation with Java 7 and 8, but this doesn't seem like it should be necessary. Java 7 does the right thing, where Java 8 gives an error even though there is an appropriate type that can be inferred for T.
请注意,在对'ternary'的调用中显式键入T确实允许使用Java 7和8进行编译,但这似乎不应该是必要的。 Java 7做了正确的事情,即使有适当的类型可以推断出来,Java 8也会出现错误。
#3
0
The problem appears in the context of parameters and assignments only. I.e.
问题仅出现在参数和分配的上下文中。即
CharSequence cs1=(true? String.class: StringBuilder.class).newInstance();
works. Unlike the other answer claims, using a generic <T> T ternary(boolean cond, T a, T b)
method does not work. This still fails when the invocation is passed to a generic method like <T> T foo(Class<T> clazz)
so that the actual type of <T>
is searched. However it works in the assignment example
作品。与其他答案声明不同,使用通用
Class<? extends CharSequence> aClass = true ? String.class : StringBuilder.class;
as the resulting type is already specified explicitly. Of course, a type cast to Class<? extends CharSequence>
would always fix that for the other use cases too.
因为结果类型已经明确指定。当然,一个类型转换为Class 总是会为其他用例修复它。
The problem is that the algorithm is specified as finding the “least upper bound” of the second and third operand first and apply “capture conversion” afterwards. The first step already fails for the two types Class<String>
and Class<StringBuilder>
so the second step which would consider the context is not even attempted.
问题是该算法被指定为首先找到第二和第三操作数的“最小上限”,然后应用“捕获转换”。对于两个类型Class
This is not a satisfying situation, however, in this special case there is an elegant alternative:
这不是一个令人满意的情况,但是,在这种特殊情况下,有一个优雅的选择:
public static void main(String... arg) {
CharSequence cs=foo(true? String::new: StringBuilder::new);
}
static <T> T foo(Supplier<T> s) { return s.get(); }
#1
11
This is not a javac bug, according to the current spec. I wrote an answer here is SO for a similar issue. Here the problem is more or less the same.
根据目前的规范,这不是一个javac bug。我在这里写的答案是针对类似问题的。这里的问题或多或少都是一样的。
On an assignment or invocation context reference conditional expressions are poly expressions. This means that the type of the expression is not the result of applying capture conversion to lub(T1, T2), see JSL-15.25.3 for a detailed definition of T1 and T2. Instead we have, also from this portion of the spec that:
在赋值或调用上下文引用中,条件表达式是多义表达式。这意味着表达式的类型不是将捕获转换应用于lub(T1,T2)的结果,有关T1和T2的详细定义,请参阅JSL-15.25.3。相反,我们也从规范的这一部分得到:
Where a poly reference conditional expression appears in a context of a particular kind with target type T, its second and third operand expressions similarly appear in a context of the same kind with target type T.
当poly引用条件表达式出现在具有目标类型T的特定类型的上下文中时,其第二和第三操作数表达式类似地出现在具有目标类型T的相同类型的上下文中。
The type of a poly reference conditional expression is the same as its target type.
多参考条件表达式的类型与其目标类型相同。
So this means that the target type is pushed down to both operands of the reference conditional expression, and both operands are attributed against that target type. So the compiler ends up gathering constraints from both operands, leading to an unsolvable constraint set and thus an error.
因此,这意味着目标类型被下推到引用条件表达式的两个操作数,并且两个操作数都归因于该目标类型。因此编译器最终从两个操作数收集约束,导致无法解决的约束集,从而导致错误。
OK, but why do we get equality bounds for T here?
好的,但为什么我们在这里得到T的相等边界?
Let's see in detail, from the call:
让我们从电话中详细了解:
foo(true ? String.class : StringBuilder.class)
where foo is:
foo在哪里:
static <T> T foo(Class<T> clazz) throws Exception {
return clazz.newInstance();
}
We have that as we are invoking method foo()
with the expression true ? String.class : StringBuilder.class
. This reference conditional expression should be compatible in a loose invocation context with type Class<T>
. This is represented as, see JLS-18.1.2:
我们有,因为我们用表达式true调用方法foo()? String.class:StringBuilder.class。此引用条件表达式应在类型为Class
true ? String.class : StringBuilder.class → Class<T>
As follows from JLS-18.2.1 we have that:
如下JLS-18.2.1我们有:
A constraint formula of the form ‹Expression → T› is reduced as follows:
形式的约束公式如下: →t>...
- If the expression is a conditional expression of the form e1 ? e2 : e3, the constraint reduces to two constraint formulas, ‹e2 → T› and ‹e3 → T›.
如果表达式是e1形式的条件表达式? e2:e3,约束减少为两个约束公式,
和 →t>。 →t>
This implies that we obtain the following constraint formulas:
这意味着我们获得了以下约束公式:
String.class → Class<T>
StringBuilder.class → Class<T>
or:
Class<String> → Class<T>
Class<StringBuilder> → Class<T>
Later from JLS-18.2.2 we have that:
后来从JLS-18.2.2我们得到:
A constraint formula of the form ‹S → T› is reduced as follows:
形式
的约束公式减少如下:→t>...
- Otherwise, the constraint reduces to ‹S <: T›.
否则,约束减少到
。
I'm only including the related parts. So going on we have now:
我只包括相关部分。所以继续我们现在:
Class<String> <: Class<T>
Class<StringBuilder> <: Class<T>
From JLS-18.2.3, we have:
从JLS-18.2.3开始,我们有:
A constraint formula of the form ‹S <: T› is reduced as follows:
形式
的约束公式减少如下:...
- Otherwise, the constraint is reduced according to the form of T:
- If T is a parameterized class or interface type, or an inner class type of a parameterized class or interface type (directly or indirectly), let A1, ..., An be the type arguments of T. Among the supertypes of S, a corresponding class or interface type is identified, with type arguments B1, ..., Bn. If no such type exists, the constraint reduces to false. Otherwise, the constraint reduces to the following new constraints: for all i (1 ≤ i ≤ n), ‹Bi <= Ai›.
如果T是参数化类或接口类型,或参数化类或接口类型(直接或间接)的内部类类型,则让A1,...,An成为T的类型参数。在S的超类型中,a识别相应的类或接口类型,类型参数为B1,...,Bn。如果不存在此类型,则约束将减少为false。否则,约束减少到以下新约束:对于所有i(1≤i≤n),
。 否则,根据T的形式减少约束:如果T是参数化类或接口类型,或参数化类或接口类型的内部类类型(直接或间接),则让A1,...,An为T的类型参数在S的超类型中,识别相应的类或接口类型,类型参数为B1,...,Bn。如果不存在此类型,则约束将减少为false。否则,约束减少到以下新约束:对于所有i(1≤i≤n),
。
So as Class<T>
, Class<String>
and Class<StringBuilder>
are parameterized classes, this implies that now our constraints reduces to:
因此Class
String <= T
StringBuilder <= T
Also from JLS-18.2.3, we have:
同样来自JLS-18.2.3,我们有:
A constraint formula of the form ‹S <= T›, where S and T are type arguments (§4.5.1), is reduced as follows:
形式
的约束公式,其中S和T是类型参数(第4.5.1节),如下所示:...
- If T is a type:
- If S is a type, the constraint reduces to ‹S = T›.
如果S是类型,则约束减少到
。如果T是类型:如果S是类型,则约束减少到
。
Thus we end up with these constraints for T:
因此我们最终得到了T的这些约束:
String = T
StringBuilder = T
Finally at JLS-18.2.4 we have that:
最后在JLS-18.2.4我们有:
A constraint formula of the form ‹S = T›, where S and T are types, is reduced as follows:
形式
的约束公式,其中S和T是类型,减少如下:...
- Otherwise, if T is an inference variable, α, the constraint reduces to the bound S = α.
否则,如果T是推理变量α,则约束减小到边界S =α。
And there is no solution for type variable T with bounds T = String
and T = StringBuilder
. There is no type the compiler can substitute T for that satisfies both restrictions. For this reason the compiler displays the error message.
对于带有边界T = String和T = StringBuilder的类型变量T,没有解决方案。编译器没有可以替代T的类型满足两个限制。因此,编译器显示错误消息。
So javac is OK according to the current spec, but is the spec correct on this? Well there is a compatibility issue between 7 and 8 that should be investigated. For this reason I have filed JDK-8044053 so we can track this issue.
所以根据当前的规范,javac是可以的,但这个规格是正确的吗?那么应该调查7到8之间的兼容性问题。出于这个原因,我已经提交了JDK-8044053,因此我们可以跟踪此问题。
#2
17
I'm going to go out on a limb and say that this error (while it may or may not conform to the updated JLS, which I admit I haven't read in detail) is due to an inconsistency in type handling by the JDK 8 compiler.
我打算说出这个错误(虽然它可能符合或不符合更新的JLS,我承认我没有详细阅读)是由于JDK的类型处理不一致8编译器。
In general the ternary operator used the same type inference as if for a two-argument method which had the formal parameters both based on the same type parameter. For instance:
通常,三元运算符使用相同的类型推断,就像对于具有基于相同类型参数的形式参数的双参数方法一样。例如:
static <T> T foo(Class<? extends T> clazz, Class<? extends T> clazz2) { return null; }
public static void main(String[] args) {
CharSequence foo2 = foo(String.class, StringBuilder.class);
}
In this example, T
can be inferred to be a capture of ? extends Object & Serializable & CharSequence
. Now similarly, in JDK 7, if we go back to your original example:
在这个例子中,T可以推断为捕获? extends Object&Serializable&CharSequence。现在同样,在JDK 7中,如果我们回到原始示例:
CharSequence foo2 = foo(true ? String.class : StringBuilder.class);
This does almost the exact same type inference as above, but in this case consider the ternary operator to be a method as such:
这几乎与上面完全相同的类型推断,但在这种情况下,将三元运算符视为一种方法:
static <T> T ternary(boolean cond, T a, T b) {
if (cond) return a;
else return b;
}
So in this case, if you pass String.class and StringBuilder.class as the parameters, the inferred type of T is (roughly speaking) Class<? extends Object & Serializable & CharSequence>
, which is what we wanted.
所以在这种情况下,如果你传递String.class和StringBuilder.class作为参数,推断的T类型(粗略地说)是Class ,这就是我们想要的。
In fact you can replace the use of your ternary operator in the original snippet with this method, thus:
实际上,您可以使用此方法替换原始代码段中三元运算符的使用,因此:
public class Foo {
public static void main(String[] args) throws Exception {
//compiles fine in Java 7 and Java 8:
Class<? extends CharSequence> aClass = true ? String.class : StringBuilder.class;
CharSequence foo = foo(aClass);
//Inlining the variable using 'ternary' method:
CharSequence foo2 = foo(ternary(true, String.class, StringBuilder.class));
}
static <T> T foo(Class<T> clazz) throws Exception {
return clazz.newInstance();
}
static <T> T ternary(boolean cond, T a, T b) {
if (cond) return a;
else return b;
}
}
... And now it compiles in Java 7 and 8 (edit: actually it also fails with Java 8! edit again: it now works, Jdk 8u20). What gives? for some reason an equality constraint is now being imposed on T (in the foo method), rather than a lower-bounds constraint. The relevant section of the JLS for Java 7 is 15.12.2.7; for Java 8 there's a whole new chapter on type inference (chapter 18).
...现在它在Java 7和8中编译(编辑:实际上它也失败了Java 8!再次编辑:它现在可以工作,Jdk 8u20)。是什么赋予了?由于某种原因,现在对T(在foo方法中)施加了等式约束,而不是下限约束。 JLS for Java 7的相关部分是15.12.2.7;对于Java 8,有一个关于类型推断的全新章节(第18章)。
Note that explicitly typing T in the call to 'ternary' does allow compilation with Java 7 and 8, but this doesn't seem like it should be necessary. Java 7 does the right thing, where Java 8 gives an error even though there is an appropriate type that can be inferred for T.
请注意,在对'ternary'的调用中显式键入T确实允许使用Java 7和8进行编译,但这似乎不应该是必要的。 Java 7做了正确的事情,即使有适当的类型可以推断出来,Java 8也会出现错误。
#3
0
The problem appears in the context of parameters and assignments only. I.e.
问题仅出现在参数和分配的上下文中。即
CharSequence cs1=(true? String.class: StringBuilder.class).newInstance();
works. Unlike the other answer claims, using a generic <T> T ternary(boolean cond, T a, T b)
method does not work. This still fails when the invocation is passed to a generic method like <T> T foo(Class<T> clazz)
so that the actual type of <T>
is searched. However it works in the assignment example
作品。与其他答案声明不同,使用通用
Class<? extends CharSequence> aClass = true ? String.class : StringBuilder.class;
as the resulting type is already specified explicitly. Of course, a type cast to Class<? extends CharSequence>
would always fix that for the other use cases too.
因为结果类型已经明确指定。当然,一个类型转换为Class 总是会为其他用例修复它。
The problem is that the algorithm is specified as finding the “least upper bound” of the second and third operand first and apply “capture conversion” afterwards. The first step already fails for the two types Class<String>
and Class<StringBuilder>
so the second step which would consider the context is not even attempted.
问题是该算法被指定为首先找到第二和第三操作数的“最小上限”,然后应用“捕获转换”。对于两个类型Class
This is not a satisfying situation, however, in this special case there is an elegant alternative:
这不是一个令人满意的情况,但是,在这种特殊情况下,有一个优雅的选择:
public static void main(String... arg) {
CharSequence cs=foo(true? String::new: StringBuilder::new);
}
static <T> T foo(Supplier<T> s) { return s.get(); }