代码在Eclipse中编译但不是javac:带有功能子接口的curried lambdas。哪个是对的?

问题描述 投票:8回答:2

我在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版本测试:

java eclipse generics lambda functional-interface
2个回答
2
投票

看起来你遇到了JDK bug JDK-8156954,它已在Java 9中修复,但在Java 8中没有。

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

  • FString(通过originalList类型的第一个参数List<String>
  • TIntToByteFunction(通过返回类型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结合使用时才会出现这种情况。


1
投票

我尝试使用javac 11.0.2的示例代码并没有收到任何错误。这表明该bug可能已经在javac中并在最近的版本中得到修复。我对此感到有些惊讶,因为如上所述,我确实尝试在在线界面中测试JDK 10。

我愿意提供其他答案,提供有关特定问题的更多详细信息,例如问题的JDK错误号。

作为在JDK 8中编译代码的变通方法,可以在内部lambda表达式中添加显式强制转换:

List<IntToByteFunction> resultList = transform(originalList,
        outer -> (IntToByteFunction) inner -> doStuff(inner, outer));
© www.soinside.com 2019 - 2024. All rights reserved.