【问题标题】:Inference variable has incompatible bounds. Java 8 Compiler Regression?推理变量具有不兼容的界限。 Java 8 编译器回归?
【发布时间】:2015-06-03 14:18:05
【问题描述】:

以下程序在 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")。那么,这是一个错误还是一个新的语言“功能”?

编辑:我也已将此问题作为 JI-9021550 报告给 Oracle,但以防万一这是 Java 8 中的“功能”,我也已向 Eclipse 报告了该问题:

【问题讨论】:

  • 嗨 Lukas,我们还不能称之为 eclipse 错误;请取消选择我的答案:(
  • @bayou.io:你是对的。完成!

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


【解决方案1】:

免责声明 - 我对这个主题知之甚少,以下是我的非正式推理,试图证明 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>

本质上,这是无法解决的。例如,如果X=List&lt;?&gt;,则不存在T

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


现在,这个解决方法是如何工作的?

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

由于通配符捕获而起作用,它引入了更多类型信息,“缩小”了instance的类型

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

不幸的是,当instanceX 时,这并没有完成,因此可以使用的类型信息较少。

可以想象,该语言也可以“改进”以对 X 进行通配符捕获:

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

【讨论】:

  • 推理期间没有通配符捕获。 X&lt;:List&lt;?&gt; 并不暗示存在W where X&lt;:List&lt;W&gt;;实际上这样的W 一定不存在。
  • 对象类型type 必须经过通配符捕获(变为Class&lt;X&gt;)才能进行推理。我记得 Eclipse 在这方面可能有点不同。
  • 问题是编译器一出现通配符就会发疯。只需将您的&lt;X extends List&lt;?&gt;&gt; 替换为&lt;Y,X extends List&lt;Y&gt;&gt;,问题就完全消失了。这里的Y? 之间没有语义上的区别,它们都表示一个完全未知的类型,它不会向场景中添加任何信息,特别是因为我们不会在其他任何地方使用Y。尽管如此,使用Y 而不是? 让编译器很高兴。
  • @Holger - 确实List&lt;?&gt;不能是超级接口,它必须是一些具体的List&lt;Y&gt;。但是,该信息不被类型系统的其他部分使用。您的解决方案明确将此信息表达给编译器。
  • X extends List&lt;?&gt; 的实例分配给List&lt;?&gt; 类型的变量并不是缩小类型,而是扩大类型。而且我不明白这是如何“引入更多类型信息”......
【解决方案2】:

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

在一个示例中,这种非法更改与 javac 完全一致的 Eclipse 行为。几代 Eclipse 开发人员比我们在 JLS 中清楚地看到的更信任这个古老的决定。今天我相信之前的偏差一定有不同的原因。

今天我鼓起勇气在这方面将 ecj 与 JLS 保持一致,瞧 5 个似乎极难破解的错误,基本上就这样解决了(加上一些小调整作为补偿)。

Ergo:是的,Eclipse 有一个错误,但该错误已在 4.7 里程碑 2 中修复 :)

以下是 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,从而导致推理失败。

【讨论】:

    【解决方案3】:

    感谢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));
    }
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多