我在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上提交错误报告?通用lambda的类型推断规则是如此复杂,以至于根据JLS,我都不知道该程序是否为合法Java。
动机说明
在原始代码中,
transform()
是番石榴的com.google.common.collect.Lists.transform()
。MyFunction
接口是Guava的com.google.common.base.Function
接口,出于历史原因,该接口扩展了java.util.function.Function
。此代码的目的是创建第一种类型的列表的视图作为第二种类型的列表。第二种类型是函数接口类型,我想用基于输入列表中的值构造的这种类型的函数填充输出列表,因此使用了咖喱的lambda表达式。
版本信息以提高可重复性
已测试的Eclipse版本:
- 2018-09(4.9.0)内部版本:20180917-1800
- 2019-03 RC1(4.11 RC1)内部版本号:20190307-2044
已测试的Javac版本:
- 1.8.0_121
- 通过JDoodle online Java compiler
的JDK 10.0.1
答案 0 :(得分:2)
您似乎遇到了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泛型结合使用时发生。
答案 1 :(得分:1)
我使用javac 11.0.2尝试了示例代码,但未收到任何错误。这表明该错误可能已经存在于javac中,并且已在最新版本中修复。我对此感到有些惊讶,因为如前所述,我确实尝试在在线界面中测试JDK 10。
我愿意接受其他答案,这些答案提供了有关特定问题的更多详细信息,例如该问题的JDK错误编号。
作为一种使代码在JDK 8中编译的解决方法,可以将显式强制转换添加到内部lambda表达式中:
List<IntToByteFunction> resultList = transform(originalList,
outer -> (IntToByteFunction) inner -> doStuff(inner, outer));