代码在Eclipse中编译,但不能在Javac中编译:带有功能子接口的咖喱lambda。哪个是正确的?

Aar*_*erg 8 java eclipse generics lambda functional-interface

我在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);
}
Run Code Online (Sandbox Code Playgroud)

在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
Run Code Online (Sandbox Code Playgroud)

将参数类型transform()从from 更改MyFunctionFunction,或删除参数类型中的通配符? extends,可使示例代码在javac中编译。

显然,Eclipse或javac都违反了Java语言规范。问题是,我应该在Eclipse还是javac上提交错误报告?通用lambda的类型推断规则是如此复杂,以至于根据JLS,我不知道该程序是否为合法Java。

动机说明

在原始代码中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)内部版本:20180917-1800
  • 2019-03 RC1(4.11 RC1)内部版本:20190307-2044

经过测试的javac版本:

how*_*ger 2

看起来您遇到了JDK bug JDK-8156954,该错误已在 Java 9 中修复,但在 Java 8 中尚未修复。

这是 Java 8 的一个错误javac,因为在您的示例中,可以在不违反 Java 语言规范的情况下推断方法的所有变量类型,如下所示:transform

  • 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);
}
Run Code Online (Sandbox Code Playgroud)

MyFunction使用相同的 ( R apply(T t);) 覆盖相同的内容。如果使用Function代替或如果扩展但没有则错误消失。也用而不是错误消失。MyFunctionMyFunctionFunction@Override R apply(T t);F? extends F

即使您的示例与上述 bug 中的示例不同,也可以假设它是相同的 bug,因为它是唯一的“参数不匹配;lambda 表达式返回类型错误”已在 Java 9 中修复但未在 Java 中修复8 并且只有 lambda 函数与 Java 泛型结合使用时才会发生这种情况。