Ale*_*øld 31 java generics 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();
}
}
Run Code Online (Sandbox Code Playgroud)
编译错误:
错误:(9,29)java:类Foo中的方法foo不能应用于给定的类型; required:java.lang.Class发现:true?Str [...]类
原因:推断类型不符合推断的等式约束:java.lang.StringBuilder等式约束:java.lang.StringBuilder,java.lang.String
为什么这在Java 8中停止工作?是故意/某些其他功能的副作用,还是仅仅是编译器错误?
dav*_*mac 17
我打算说出这个错误(虽然它可能符合或不符合更新的JLS,我承认我没有详细阅读)是由于JDK的类型处理不一致8编译器.
通常,三元运算符使用相同的类型推断,就像对于具有基于相同类型参数的形式参数的双参数方法一样.例如:
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);
}
Run Code Online (Sandbox Code Playgroud)
在这个例子中,T
可以推断是一个捕获? extends Object & Serializable & CharSequence
.现在同样,在JDK 7中,如果我们回到原始示例:
CharSequence foo2 = foo(true ? String.class : StringBuilder.class);
Run Code Online (Sandbox Code Playgroud)
这几乎与上面完全相同的类型推断,但在这种情况下,将三元运算符视为一种方法:
static <T> T ternary(boolean cond, T a, T b) {
if (cond) return a;
else return b;
}
Run Code Online (Sandbox Code Playgroud)
所以在这种情况下,如果你传递String.class和StringBuilder.class作为参数,推断的T类型(粗略地说)Class<? extends Object & Serializable & CharSequence>
,这是我们想要的.
实际上,您可以使用此方法替换原始代码段中三元运算符的使用,因此:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
...现在它在Java 7中编译 和8 (编辑:实际上它也失败了Java 8!再次编辑:它现在有效,Jdk 8u20).是什么赋予了?由于某种原因,现在正在对T(在foo方法中)施加等式约束,而不是下限约束.JLS for Java 7的相关部分是15.12.2.7 ; 对于Java 8,有一个关于类型推断的全新章节(第18章).
请注意,在对'ternary'的调用中显式键入T确实允许使用Java 7和8进行编译,但这似乎不应该是必要的.Java 7做了正确的事情,即使有一个适当的类型可以推断出来,Java 8也会出错.
Vic*_*ero 11
根据目前的规范,这不是一个javac bug.我在这里写的答案是针对类似问题的.这里的问题或多或少都是一样的.
在赋值或调用上下文引用中,条件表达式是多义表达式.这意味着表达式的类型不是将捕获转换应用于lub(T1,T2)的结果,有关T1和T2的详细定义,请参阅JSL-15.25.3.相反,我们也从规范的这一部分得到:
当poly引用条件表达式出现在具有目标类型T的特定类型的上下文中时,其第二和第三操作数表达式类似地出现在具有目标类型T的相同类型的上下文中.
多参考条件表达式的类型与其目标类型相同.
因此,这意味着目标类型被下推到引用条件表达式的两个操作数,并且两个操作数都归因于该目标类型.因此编译器最终会从两个操作数收集约束,从而导致无法解析的约束集,从而导致错误.
好的,但为什么我们在这里获得T的相等界限?
让我们从电话中详细了解:
foo(true ? String.class : StringBuilder.class)
Run Code Online (Sandbox Code Playgroud)
foo在哪里:
static <T> T foo(Class<T> clazz) throws Exception {
return clazz.newInstance();
}
Run Code Online (Sandbox Code Playgroud)
我们有这个,因为我们foo()
用表达式调用方法true ? String.class : StringBuilder.class
.此引用条件表达式应与具有类型的松散调用上下文兼容Class<T>
.这表示为,见JLS-18.1.2:
true ? String.class : StringBuilder.class ? Class<T>
Run Code Online (Sandbox Code Playgroud)
如下JLS-18.2.1我们有:
<Expression→T>形式的约束公式如下:
...
- 如果表达式是e1形式的条件表达式?e2:e3,约束减少为两个约束公式,<e2→T>和<e3→T>.
这意味着我们获得以下约束公式:
String.class ? Class<T>
StringBuilder.class ? Class<T>
Run Code Online (Sandbox Code Playgroud)
要么:
Class<String> ? Class<T>
Class<StringBuilder> ? Class<T>
Run Code Online (Sandbox Code Playgroud)
后来从JLS-18.2.2我们得到:
形式<S→T>的约束公式减少如下:
...
- 否则,约束减少到<S <:T>.
我只包括相关部分.所以继续我们现在:
Class<String> <: Class<T>
Class<StringBuilder> <: Class<T>
Run Code Online (Sandbox Code Playgroud)
从JLS-18.2.3开始,我们有:
形式<S <:T>的约束公式减少如下:
...
- 否则,根据T的形式减少约束:
- 如果T是参数化类或接口类型,或参数化类或接口类型(直接或间接)的内部类类型,则让A1,...,An成为T的类型参数.在S的超类型中,a识别相应的类或接口类型,类型参数为B1,...,Bn.如果不存在此类型,则约束将减少为false.否则,约束减少到以下新约束:对于所有i(1≤i≤n),<Bi <= Ai>.
这样Class<T>
,Class<String>
和Class<StringBuilder>
是参数化的类,这意味着我们现在的约束条件简化为:
String <= T
StringBuilder <= T
Run Code Online (Sandbox Code Playgroud)
同样来自JLS-18.2.3,我们有:
形式<S <= T>的约束公式,其中S和T是类型参数(第4.5.1节),减少如下:
...
- 如果T是一个类型:
- 如果S是类型,则约束减少到<S = T>.
因此我们最终得到了T的这些约束:
String = T
StringBuilder = T
Run Code Online (Sandbox Code Playgroud)
最后在JLS-18.2.4我们有:
形式<S = T>的约束公式,其中S和T是类型,减少如下:
...
- 否则,如果T是推理变量α,则约束减小到边界S =α.
对于带有边界T = String
和类型的类型变量T,没有解决方案T = StringBuilder
.编译器没有可以替代T的类型满足两个限制.因此,编译器显示错误消息.
所以根据当前的规范,javac是可以的,但这个规格是正确的吗?那么应该调查7到8之间的兼容性问题.因此我提交了JDK-8044053,以便我们跟踪此问题.