除非指定了参数类型,否则Java无法编译通用lambda参数

问题描述 投票:7回答:2

在测试时,我将我的Junit升级到5.0(因此用新版本替换了我的一些assertTrue()方法)。在这样做之后,我发现我的一个测试没有编译。我将问题简化为普通的旧java,没有junit或其他依赖项。结果是以下代码无法编译:

  public static void recreate() {
    // This does NOT work
    Recreation.assertTrue(identity((x) -> Boolean.TRUE)); 
    // This DOES work
    Recreation.assertTrue(identity((String x) -> Boolean.TRUE)); 
  }

  private static class Recreation {
    public static void assertTrue(boolean b) {
      System.out.println("boolean argument: " + b);
    }

    // If this method is removed, the code will compile. 
    public static void assertTrue(Supplier<Boolean> booleanSupplier) {
      System.out.println("supplier argument: " + booleanSupplier.toString());
    }
  }

  private static <K> K identity(Function<String, K> function) {
    return function.apply("hello");
  }

如上例所示,如果满足以下任一条件,代码将编译:

  1. 指定了lambda参数类型
  2. 删除了重载的as​​sertTrue(Supplier booleanSupplier)方法

这是类型推断/擦除的问题,还是可能会发生什么?

构建错误:

Error:(10, 35) incompatible types: inference variable K has incompatible bounds
    lower bounds: java.util.function.Supplier<java.lang.Boolean>,java.lang.Object
    lower bounds: java.lang.Boolean

眼镜:

openjdk version "11.0.1" 2018-10-16
OpenJDK Runtime Environment (build 11.0.1+13-Ubuntu-3ubuntu114.04ppa1)
OpenJDK 64-Bit Server VM (build 11.0.1+13-Ubuntu-3ubuntu114.04ppa1, mixed mode, sharing)

OS: Ubuntu 14.04.5 LTS

编辑:确认Java 8上也存在问题:

java version "1.8.0_31"
Java(TM) SE Runtime Environment (build 1.8.0_31-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.31-b07, mixed mode)
exit status 1
Main.java:10: error: incompatible types: inferred type does not conform to upper bound(s)
    Recreation.assertTrue(identity((x) -> Boolean.TRUE));
                                  ^
    inferred: Boolean
    upper bound(s): Supplier<Boolean>,Object
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error
java generics type-inference
2个回答
4
投票

环顾四周并在这里阅读Java语言规范https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.1

我认为这里有两个步骤:

首先,重载分辨率无法推断出identity((x) -> Boolean.TRUE)的类型,因为它是隐含的lambda,我认为为简单起见,它没有被考虑在内。因此,它将扩大参数搜索并使用public static void assertTrue(Supplier<Boolean> booleanSupplier)

其次,在完成重载分辨率后,类型推断就会启动。这次它确实检查了Boolean的推断类型,并且因为它与Supplier<Boolean> booleanSupplier不兼容,所以你得到了编译错误。

像以前的答案一样,有解决方案,

e.g

Recreation.assertTrue(identity((x) -> () -> Boolean.TRUE));

我在这里找到了一个很好的解释:Java8: ambiguity with lambdas and overloaded methods


0
投票

经过广泛的研究,我偶然发现了this bug report。该报告引用了Java规范中的this section,它为您提供了有关所有这些东西如何工作的基本知识。请记住,因为以后需要它。

问题可以减少到这样(即使我知道这不是你想要的,它会重现错误):

Supplier<Boolean> o = identity((x) -> true);

所以,我认为这个问题引起了Java如何决定泛型应该是什么类型的共鸣。当你指定Supplier<Boolean>作为o的类型时,它告诉编译器identity的返回类型应该是Supplier<Boolean>

现在,在您的示例中,您没有存储identity输出的变量。这是来自规范的部分。每当Java获得泛型类型时,该类型都需要在特定范围内。这基本上意味着扩展层次结构中存在特定的最高类和最低类。

例如,如果你有类C扩展类B,它也扩展类A,你的上限可能是C,下限可能是A。这样你可以使用从AC的任何东西。但这仅仅是边界如何工作的一个例子。

这基本上是你班上发生的事情。由于您没有将String指定为参数的类型,因此Java不知道可能的类型。由于它不知道,它尽力将它强制转换为正确的类型,但由于泛型是如此模糊,它无法将其转换为正确的类型。它以某种方式决定使用Supplier<Boolean>作为基线(也可能在规范中的某处定义),并期望它作为返回类型。因为它没有得到它,它会引发错误。为什么它不决定检查它是否是Boolean超出我,但根据错误报告,一切都按预期工作。

一些可能的修复可能看起来像:

Recreation.assertTrue(identity((Function<String, Boolean>) (x) -> Boolean.TRUE));
Recreation.assertTrue((Boolean) identity((x) -> Boolean.TRUE));

或者只是隐含地告诉它参数是什么类型。

Recreation.assertTrue(identity((String x) -> Boolean.TRUE));

我知道我对这种具体情况没有最好的理解,但这应该至少让你对整个系统的运作方式有了基本的了解。

© www.soinside.com 2019 - 2024. All rights reserved.