LambdaConversionException with generics: JVM bug?

2022-09-01 18:25:40

我有一些带有方法引用的代码,该方法编译良好并在运行时失败。

例外情况是:

Caused by: java.lang.invoke.LambdaConversionException: Invalid receiver type class redacted.BasicEntity; not a subtype of implementation type interface redacted.HasImagesEntity
    at java.lang.invoke.AbstractValidatingLambdaMetafactory.validateMetafactoryArgs(AbstractValidatingLambdaMetafactory.java:233)
    at java.lang.invoke.LambdaMetafactory.metafactory(LambdaMetafactory.java:303)
    at java.lang.invoke.CallSite.makeSite(CallSite.java:289)

触发异常的类:

class ImageController<E extends BasicEntity & HasImagesEntity> {
    void doTheThing(E entity) {
        Set<String> filenames = entity.getImages().keySet().stream()
            .map(entity::filename)
            .collect(Collectors.toSet());
    }
}

尝试解决 时引发异常。 在 中声明。据我所知,我得到了例外,因为E的擦除是和JVM不(不能?)考虑E上的其他边界。entity::filenamefilename()HasImagesEntityBasicEntity

当我将方法引用重写为一个简单的 lambda 时,一切都很好。在我看来,一个结构按预期工作,而它的语义等价物爆炸了,这似乎真的很可疑。

这可能在规范中吗?我非常努力地想找到一种方法,使这不会成为编译器或运行时的问题,并且没有提出任何东西。


答案 1

下面是一个简化的示例,它重现了问题并仅使用核心 Java 类:

public static void main(String[] argv) {
    System.out.println(dummy("foo"));
}
static <T extends Serializable&CharSequence> int dummy(T value) {
    return Optional.ofNullable(value).map(CharSequence::length).orElse(0);
}

您的假设是正确的,特定于 JRE 的实现将目标方法作为方法处理接收,该方法处理程序没有有关泛型类型的信息。因此,它唯一看到的是原始类型不匹配。

与许多泛型构造一样,在字节码级别上需要一个类型转换,该类型转换不会出现在源代码中。由于 LambdaMetafactory 显式需要直接方法句柄,因此封装此类类型强制转换的方法引用不能作为 a 传递给工厂。MethodHandle

有两种可能的方法可以处理它。

第一个解决方案是将 更改为信任(如果接收方类型为 an),并在生成的 lambda 类中插入所需的类型,而不是拒绝它。毕竟,它已经对参数和返回类型进行了类似的操作。LambdaMetafactoryMethodHandleinterface

或者,编译器将负责创建一个综合帮助器方法,用于封装类型转换和方法调用,就像您编写了 lambda 表达式一样。这不是一个独特的情况。例如,如果您使用对 varargs 方法或数组创建的方法引用,例如,它们不能表示为直接方法句柄,并最终出现在合成帮助器方法中。String[]::new

在任何一种情况下,我们都可以将当前行为视为错误。但显然,编译器和JRE开发人员必须就应该以哪种方式处理它达成一致,然后我们才能说出错误位于哪一边。


答案 2

我刚刚在JDK9和JDK8u45中修复了这个问题。请参阅此错误。更改需要一些时间才能渗透到升级的版本中。Dan 只是向我指出了这个 Stack Overflow 问题,所以我添加了这个注释 当你发现 bug 时,请提交它们。

我通过让编译器创建一个桥来解决这个问题,就像许多复杂方法引用的情况一样。我们还在研究规范含义。


推荐