推理变量具有不兼容的边界。 Java 8编译器回归?

时间:2015-06-03 14:18:05

标签: java compiler-errors java-8 type-inference

以下程序在Java 7和Eclipse Mars RC2 for Java 8中编译:

import java.util.List;

public class Test {

    static final void a(Class<? extends List<?>> type) {
        b(newList(type));
    }

    static final <T> List<T> b(List<T> list) {
        return list;
    }

    static final <L extends List<?>> L newList(Class<L> type) {
        try {
            return type.newInstance();
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

使用javac 1.8.0_45编译器报告以下编译错误:

Test.java:6: error: method b in class Test cannot be applied to given types;
        b(newList(type));
        ^
  required: List<T>
  found: CAP#1
  reason: inference variable L has incompatible bounds
    equality constraints: CAP#2
    upper bounds: List<CAP#3>,List<?>
  where T,L are type-variables:
    T extends Object declared in method <T>b(List<T>)
    L extends List<?> declared in method <L>newList(Class<L>)
  where CAP#1,CAP#2,CAP#3 are fresh type-variables:
    CAP#1 extends List<?> from capture of ? extends List<?>
    CAP#2 extends List<?> from capture of ? extends List<?>
    CAP#3 extends Object from capture of ?

解决方法是在本地分配变量:

import java.util.List;

public class Test {

    static final void a(Class<? extends List<?>> type) {

        // Workaround here
        List<?> variable = newList(type);
        b(variable);
    }

    static final <T> List<T> b(List<T> list) {
        return list;
    }

    static final <L extends List<?>> L newList(Class<L> type) {
        try {
            return type.newInstance();
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

我知道Java 8中的类型推断发生了很大的变化(e.g. due to JEP 101 "generalized target-type inference")。那么,这是一个错误还是一种新语言&#34;功能&#34;?

编辑:我还向甲骨文报告了这个问题,作为JI-9021550,但以防这是一个&#34;功能&#34;在Java 8中,我也向Eclipse报告了这个问题:

3 个答案:

答案 0 :(得分:7)

免责声明 - 我对这个主题不够了解,以下是我的非正式推理,试图证明javac的行为是合理的。

我们可以将问题减少到

<X extends List<?>> void a(Class<X> type) throws Exception
{
    X instance = type.newInstance();
    b(instance);  // error
}

<T> List<T> b(List<T> list) { ... }

要推断T,我们有约束

      X <: List<?>
      X <: List<T>

基本上,这是无法解决的。例如,如果T,则不存在X=List<?>

不确定Java7如何推断这种情况。但是javac8(和IntelliJ)表现得很合理,我会说。

现在,这个解决方法如何运作?

    List<?> instance = type.newInstance();
    b(instance);  // ok!

它的工作原因是通配符捕获,它引入了更多的类型信息,&#34;缩小&#34; instance

的类型
    instance is List<?>  =>  exist W, where instance is List<W>  =>  T=W

不幸的是,instanceX时未执行此操作,因此可以使用较少的类型信息。

可以想象,语言可以得到改善&#34;也为X做通配符捕获:

    instance is X, X is List<?>  =>  exist W, where instance is List<W>

答案 1 :(得分:6)

感谢bug report,感谢Holger,感谢你的回答。这些和其他几个人最后让我质疑11年前在Eclipse编译器中做出的一个小改动。关键是:Eclipse非法扩展了捕获算法,以递归方式应用于通配符边界。

有一个例子,这个非法的变化完全符合Eclipse与javac的行为。与JLS中我们能够清楚看到的一样,Eclipse开发人员已经相信这个旧决定。今天我认为以前的偏差必然有不同的原因。

今天我鼓起勇气让ecj与JLS在这方面保持一致,并且5个看起来非常难以破解的错误,基本上已经解决了这个问题(加上一点点调整以及补偿)。

Ergo:是的,Eclipse有一个bug,但是从4.7里程碑2开始修复了这个bug:)

以下是ecj今后将报告的内容:

The method b(List<T>) in the type Test is not applicable for the arguments (capture#1-of ? extends List<?>)

捕获范围内的通配符没有找到检测兼容性的规则。更确切地说,在推理过程中的某些时间(精确地合并)我们遇到以下约束(T#0表示推理变量):

⟨T#0 = ?⟩

天真地,我们可以将类型变量解析为通配符,但是 - 可能是因为通配符不被视为类型 - 减少规则将上述定义为减少为FALSE,从而使推理失败。

答案 2 :(得分:5)

感谢bayou.io’s answer我们可以将问题缩小到

这一事实
<X extends List<?>> void a(X instance) {
    b(instance);  // error
}
static final <T> List<T> b(List<T> list) {
    return list;
}

时出现错误

<X extends List<?>> void a(X instance) {
    List<?> instance2=instance;
    b(instance2);
}
static final <T> List<T> b(List<T> list) {
    return list;
}

可以毫无问题地编译。 instance2=instance的赋值是一种扩展转换,对于方法调用参数也应该发生。因此this answer模式的差异是附加的子类型关系。

请注意,虽然我不确定这个特定情况是否与Java语言规范一致,但是一些测试显示Eclipse接受代码的原因可能是它通常对于泛型类型更为草率,以下,绝对不正确的代码可以编译,没有任何错误或警告:

public static void main(String... arg) {
    List<Integer> l1=Arrays.asList(0, 1, 2);
    List<String>  l2=Arrays.asList("0", "1", "2");
    a(Arrays.asList(l1, l2));
}
static final void a(List<? extends List<?>> type) {
    test(type);
}
static final <Y,L extends List<Y>> void test(List<L> type) {
    L l1=type.get(0), l2=type.get(1);
    l2.set(0, l1.get(0));
}