我在Eclipse中开发了一些代码,成功测试了它,将它推送到我们的Jenkins CI服务器,并收到一封Maven因Java编译错误而窒息的电子邮件。我随后隔离了问题并创建了以下显示问题的最小示例:
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,我不知道这个程序是否是合法的Java。
动机笔记
在原始代码中,
transform()
是番石榴的com.google.common.collect.Lists.transform()
。MyFunction
接口是Guava的com.google.common.base.Function
接口,由于历史原因扩展了java.util.function.Function
。此代码的目的是创建第一种类型列表的视图作为第二种类型的列表。第二种类型是一个功能接口类型,我想用输入列表中的值构建这种类型的函数来填充输出列表 - 因此是curried lambda表达式。
版本信息的再现性
测试的Eclipse版本:
- 2018-09(4.9.0)Build id:20180917-1800
- 2019-03 RC1(4.11 RC1)Build id:20190307-2044
javac版本测试:
- 1.8.0_121
- JDK 10.0.1通过JDoodle online Java compiler
看起来你遇到了JDK bug JDK-8156954,它已在Java 9中修复,但在Java 8中没有。
这是Java 8 javac
的一个错误,因为在您的示例中,可以推断出所有变量类型的transform
方法,而不违反Java语言规范,如下所示:
F
:String
(通过originalList
类型的第一个参数List<String>
)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表达式错误中的错误返回类型已在Java 9中修复但在Java中未修复8,仅在lambda函数与Java Generics结合使用时才会出现这种情况。
我尝试使用javac 11.0.2的示例代码并没有收到任何错误。这表明该bug可能已经在javac中并在最近的版本中得到修复。我对此感到有些惊讶,因为如上所述,我确实尝试在在线界面中测试JDK 10。
我愿意提供其他答案,提供有关特定问题的更多详细信息,例如问题的JDK错误号。
作为在JDK 8中编译代码的变通方法,可以在内部lambda表达式中添加显式强制转换:
List<IntToByteFunction> resultList = transform(originalList,
outer -> (IntToByteFunction) inner -> doStuff(inner, outer));