在Java 8中使用三元运算符进行泛型编译错误,但在Java 7中没有

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,以便我们跟踪此问题.