我在Eclipse中开发了一些代码,成功测试了它,将其推向了我们的Jenkins CI服务器,并收到了一封电子邮件,Maven正在用Java编译错误cho住。随后,我隔离了这个问题,并创建了以下最小示例,显示了问题:
import java.util.List;
import java.util.function.Function;
class MinimalTypeFailureExample {
public static void main(String[] args) {
List<String> originalList = null; // irrelevant
List<IntToByteFunction> resultList = transform(originalList,
outer -> inner -> doStuff(inner, outer));
System.out.println(resultList);
}
static <F, T> List<T> transform(List<F> originalList,
MyFunction<? super F, ? extends T> function) {
return null; // irrelevant
}
static Byte doStuff(Integer inner, String outer) {
return null; // irrelevant
}
}
@FunctionalInterface
interface MyFunction<F, T> extends Function<F, T> {
@Override
T apply(F input);
}
@FunctionalInterface
interface IntToByteFunction {
Byte applyIntToByte(Integer inner);
}
在Eclipse中,此代码无错误地编译,并且似乎按预期执行。但是,使用Javac编译给出以下错误:
MinimalTypeFailureExample.java:7: error: incompatible types: cannot infer type-variable(s) F,T
List<IntToByteFunction> resultList = transform(originalList, outer -> inner -> doStuff(inner, outer));
^
(argument mismatch; bad return type in lambda expression
T is not a functional interface)
where F,T are type-variables:
F extends Object declared in method <F,T>transform(List<F>,MyFunction<F,? extends T>)
T extends Object declared in method <F,T>transform(List<F>,MyFunction<F,? extends T>)
1 error
将transform()
的参数类型从MyFunction
更改为Function
,或在参数类型中删除通配符? extends
,使示例代码在Javac中编译。
显然,Eclipse或Javac都违反了Java语言规范。问题是,我是否在Eclipse或Javac 上提交错误报告?通用lambdas的类型推理规则是如此复杂,以至于我不知道该程序是否是根据JLS进行的。
。动机注意
在原始代码中,
transform()
是Guava的com.google.common.collect.Lists.transform()
。MyFunction
接口是Guava的com.google.common.base.Function
接口,它由于历史原因而扩展java.util.function.Function
。此代码的目的是创建第一个类型列表的视图,作为第二种类型的列表。第二种类型是一种功能接口类型,我想根据输入列表中的值构建的该类型的功能填充输出列表,因此,咖喱lambda表达式。
可重复性的版本信息
Eclipse版本测试:
- 2018-09(4.9.0)构建ID:20180917-1800
- 2019-03 RC1(4.11 RC1)构建ID:20190307-2044
测试的Javac版本:
- 1.8.0_121
- JDK 10.0.1通过Jdoodle在线Java编译器
看起来您遇到 JDK BUG JDK-8156954 已固定在Java 9中,但不在Java 8中。
这是Java 8 javac
的错误,因为在您的示例中,可以推断出transform
方法的所有变量类型 可以在不违反Java语言规范的情况下推断出:
-
F
:String
(通过类型List<String>
的第一个参数originalList
) -
T
:IntToByteFunction
(通过返回类型List<IntToByteFunction>
)
这些推断的变量类型与兼容第二个参数的类型,链接的lambda 表达式:
-
outer -> inner -> doStuff(inner, outer)
解决方案(使用doStuff(Integer, String)
到 -
String -> Integer -> doStuff(Integer, String)
解决 -
String -> Integer -> Byte
与
兼容 -
String -> IntToByteFunction
与
兼容 -
MyFunction<? super String, ? extends IntToByteFunction>
您的示例可以进一步最小化:
import java.util.function.Function;
class MinimalTypeFailureExample {
void foo() {
transform((Function<Integer, String>)null, o -> i -> {return "";});
}
<T, F> void transform(F f, MyFunction<T, ? extends F> m) {}
}
@FunctionalInterface
interface MyFunction<T, R> extends Function<T, R> {
@Override
R apply(T t);
}
MyFunction
用相同的(R apply(T t);
)覆盖相同。如果使用Function
而不是MyFunction
,或者MyFunction
扩展了Function
,但没有@Override R apply(T t);
,则错误会消失。同样,使用F
而不是? extends F
,错误消失了。
即使您的示例与上述错误中的示例有所不同,也可以假定它是同一错误,因为它是唯一的"参数不匹配; lambda expression in lambda expression bug中的错误返回类型已固定在Java 9中,但不在Java 8中固定,仅与Java Generics结合使用Lambda功能。
我尝试使用Javac 11.0.2尝试了示例代码,但没有收到任何错误。这表明该错误可能是在Javac中,并且在最近的版本中已固定。我对此有些惊讶,因为如前所述,我确实尝试了在线界面中测试JDK 10。
我对其他答案开放,这些答案提供了有关特定问题的更多详细信息,例如该问题的JDK错误号。
作为使代码编译在JDK 8中的解决方法,可以将显式铸件添加到Inner Lambda表达式:
List<IntToByteFunction> resultList = transform(originalList,
outer -> (IntToByteFunction) inner -> doStuff(inner, outer));