当前位置: 首页 > 知识库问答 >
问题:

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

焦宁
2023-03-14

以下程序在Java7和Eclipse Mars RC2中编译Java8:

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

我知道类型推断在Java8中发生了很大变化(例如,由于JEP 101“广义目标类型推断”)。那么,这是一个错误还是一个新的语言“功能”?

编辑:我也向Oracle报告了JI-9021550,但以防万一这是Java8中的一个“功能”,我也向Eclipse报告了这个问题:

  • https://bugs.eclipse.org/bugs/show_bug.cgi?id=469297

共有3个答案

那正初
2023-03-14

由于bayou.io的回答,我们可以把问题缩小到

<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=实例的赋值是一个扩展的转换,这也应该发生在方法调用参数上。所以这个答案模式的不同之处在于附加的子类型关系。

请注意,虽然我不确定这种特定情况是否符合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));
}
姜俊友
2023-03-14

感谢你的错误报告,也感谢霍尔格在你的回答中给出的例子。这些和其他几个问题最终让我对11年前Eclipse编译器中的一个小改动产生了疑问。重点是:Eclipse非法扩展了捕获算法,以递归方式应用于通配符边界。

有一个例子表明,这种非法更改使Eclipse行为与javac完全一致。几代Eclipse开发人员对这个老决定的信任超过了我们在JLS中可以清楚看到的。今天,我相信之前的偏差一定有不同的原因。

今天,我鼓起勇气在这方面将ecj与JLS对齐,瞧,5个看起来极其难以破解的错误,基本上就这样得到了解决(加上一点调整作为补偿)。

因此:是的,Eclipse有一个bug,但是这个bug在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,从而导致推理失败。

宋岳
2023-03-14

免责声明——我对这个主题了解不够,下面是我的一个非正式推理,试图证明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,则不存在T

不确定Java7如何推断这种情况。但是javac8(和IntelliJ)的行为是合理的。

现在,这个变通方法是如何工作的?

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

它之所以能工作,是因为通配符捕获引入了更多类型信息,“缩小”了实例的类型

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

不幸的是,当实例X时,这并没有完成,因此需要处理的类型信息较少。

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

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

 类似资料:
  • 错误:(65,52)java:不兼容类型:推理变量U的边界不兼容等式约束:akka。http。javadsl。模型HttpResponse下限:com。我的演员。聊天演员。聊天信息 下面这行代码显示了错误: 这里是HttpResponse是Akka Http的。 我不知道它在说什么。解决它的方法应该是什么?

  • JDK 1.8 设置和收集。 我想数一数那个十字路口 我试试这个: 但我有一个错误:

  • 我有以下代码 出于某种原因,它抛出了以下编译错误 Solution.java:11:错误:不兼容类型:推断变量T具有不兼容的边界List=Arrays.asList(A);^相等约束:整数下界:int[]其中T是类型变量:T扩展方法中声明的Object asList(T...) 我假设这是一个Java8功能但我不知道如何解决这个错误

  • 我有一个树对象,它包含树对象的子对象(HashMap),等等 我需要按numericPosition变量筛选对象 例如: 在这种情况下,我应该得到一个树对象过滤的数字位置 树类 以防 我得到这个错误:错误:不兼容类型:推断变量R具有不兼容的边界 我一直在遵循这个例子,但它对我不起作用。https://www.mkyong.com/java8/java-8-filter-a-map-examples

  • 正如斯图尔特·马克斯(Stuart Marks)指出的那样,我试图为这里所述的问题编写一个解决方案,以使用一个helper类。由于这个错误,我被代码卡住了: 我当前的代码: 我本人对Java 8很陌生,解决方案可能是错误的,但我不知道错误是什么意思,也不知道如何解决它。元组类过去也是泛型的,但因为我认为这导致了问题,我删除了泛型类型,但仍然存在相同的错误。

  • 我已经复习了“可能相关”的问题,但它们似乎没有帮助;我也是一个初学者,尤其是Java8,所以请耐心听我说! 问题是,我想将一个包含值列表的文件(在本例中是双数字)读入一个数组。我在助手类中作为方法来做这些,因为我希望在我的项目中需要多次这样做。 我已经创建了一个将文件转换为二维双数组的简单方法- 这很有效,我一直在成功地使用它。然后我想尝试将一个包含一长串大数字的文件读入一个1D数组,所以尝试快速