Java 除非指定参数类型,否则无法编译通用 lambda 参数
Java cannot compile generic lambda argument unless parameter type is specified
在测试时,我将我的 Junit 升级到 5.0(因此用新版本替换了我的一些 assertTrue() 方法)。这样做之后,我发现我的一个测试没有编译。我将问题减少到没有 junit 或其他依赖项的普通旧 java。结果是以下无法编译的代码:
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");
}
如上例所示,如果满足以下任一条件,代码将编译:
指定lambda参数类型
删除了重载的 assertTrue(Supplier booleanSupplier) 方法
这是类型 inference/erasure 的问题还是这里可能发生了什么?
构建错误:
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 规范中偶然发现了 this bug report. That report references this section,它为您提供了所有这些东西如何工作的基本知识。记在心里,因为以后会用到。
问题可以简化为如下所示(即使我知道这不是你想要的,它会重现错误):
Supplier<Boolean> o = identity((x) -> true);
所以,我认为这个问题与 Java 如何决定泛型应该是什么类型有关。当你指定 Supplier<Boolean>
作为 o
的类型时,它告诉编译器 identity
的 return 类型应该是 Supplier<Boolean>
.
现在,在您的示例中,您没有存储 identity
输出的变量。这是来自规范的部分。每当 Java 获得通用类型时,该类型需要在特定范围内。这基本上意味着在扩展层次结构中有特定的最高 class 和最低 class。
例如,如果您有 class C
扩展了 class B
,它也扩展了 class A
,您的上限可以是 C
,下限可以是 A
。这样你就可以使用从 A
到 C
的任何东西。不过,这只是边界工作原理的一个示例。
这基本上就是您 class 中正在发生的事情。由于您没有将 String
指定为参数的类型,因此 Java 不知道可能是什么类型。由于它不知道,所以它会尽力将其转换为正确的类型,但由于泛型非常模糊,因此无法将其转换为正确的类型。它以某种方式决定使用 Supplier<Boolean>
作为基线(可能也在规范中的某处定义),并期望它是 return 类型。因为它没有得到,它会抛出一个错误。为什么它不决定检查它是否是 Boolean
超出了我的范围,但根据错误报告,一切都按预期工作。
一些潜在的修复可能如下所示:
Recreation.assertTrue(identity((Function<String, Boolean>) (x) -> Boolean.TRUE));
Recreation.assertTrue((Boolean) identity((x) -> Boolean.TRUE));
或者只是隐式地告诉它参数是什么类型。
Recreation.assertTrue(identity((String x) -> Boolean.TRUE));
我知道我对这种特定情况没有最好的了解,但这至少应该让您对整个系统的工作原理有一个基本的了解。
环顾四周并阅读 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
,你会得到编译错误。
和之前的回答一样,有解决办法,
例如
Recreation.assertTrue(identity((x) -> () -> Boolean.TRUE));
我在这里找到了很好的解释:Java8: ambiguity with lambdas and overloaded methods
在测试时,我将我的 Junit 升级到 5.0(因此用新版本替换了我的一些 assertTrue() 方法)。这样做之后,我发现我的一个测试没有编译。我将问题减少到没有 junit 或其他依赖项的普通旧 java。结果是以下无法编译的代码:
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");
}
如上例所示,如果满足以下任一条件,代码将编译:
指定lambda参数类型
删除了重载的 assertTrue(Supplier booleanSupplier) 方法
这是类型 inference/erasure 的问题还是这里可能发生了什么?
构建错误:
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 规范中偶然发现了 this bug report. That report references this section,它为您提供了所有这些东西如何工作的基本知识。记在心里,因为以后会用到。
问题可以简化为如下所示(即使我知道这不是你想要的,它会重现错误):
Supplier<Boolean> o = identity((x) -> true);
所以,我认为这个问题与 Java 如何决定泛型应该是什么类型有关。当你指定 Supplier<Boolean>
作为 o
的类型时,它告诉编译器 identity
的 return 类型应该是 Supplier<Boolean>
.
现在,在您的示例中,您没有存储 identity
输出的变量。这是来自规范的部分。每当 Java 获得通用类型时,该类型需要在特定范围内。这基本上意味着在扩展层次结构中有特定的最高 class 和最低 class。
例如,如果您有 class C
扩展了 class B
,它也扩展了 class A
,您的上限可以是 C
,下限可以是 A
。这样你就可以使用从 A
到 C
的任何东西。不过,这只是边界工作原理的一个示例。
这基本上就是您 class 中正在发生的事情。由于您没有将 String
指定为参数的类型,因此 Java 不知道可能是什么类型。由于它不知道,所以它会尽力将其转换为正确的类型,但由于泛型非常模糊,因此无法将其转换为正确的类型。它以某种方式决定使用 Supplier<Boolean>
作为基线(可能也在规范中的某处定义),并期望它是 return 类型。因为它没有得到,它会抛出一个错误。为什么它不决定检查它是否是 Boolean
超出了我的范围,但根据错误报告,一切都按预期工作。
一些潜在的修复可能如下所示:
Recreation.assertTrue(identity((Function<String, Boolean>) (x) -> Boolean.TRUE));
Recreation.assertTrue((Boolean) identity((x) -> Boolean.TRUE));
或者只是隐式地告诉它参数是什么类型。
Recreation.assertTrue(identity((String x) -> Boolean.TRUE));
我知道我对这种特定情况没有最好的了解,但这至少应该让您对整个系统的工作原理有一个基本的了解。
环顾四周并阅读 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
,你会得到编译错误。
和之前的回答一样,有解决办法,
例如
Recreation.assertTrue(identity((x) -> () -> Boolean.TRUE));
我在这里找到了很好的解释:Java8: ambiguity with lambdas and overloaded methods