带泛型的LambdaConversionException:JVM错误? [英] LambdaConversionException with generics: JVM bug?

查看:658
本文介绍了带泛型的LambdaConversionException:JVM错误?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



例外如下:

 引发:java.lang.invoke.LambdaConversionException:无效的接收器类型类redacted.BasicEntity;不是实现类型接口的子类型java.lang.invoke.AbstractValidatingLambdaMetafactory.validateMetafactoryArgs(AbstractValidatingLambdaMetafactory.java:233)上的redacted.HasImagesEntity 
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());




$ b $ p
$ b

试图解析entity :: filename 。 filename()在HasImagesEntity上声明。据我所知,我得到了这个例外,因为E的擦除是BasicEntity,而JVM没有(不能)?考虑E上的其他边界。



当我将方法引用重写为一个简单的lambda时,一切都很好。对我来说,一个构造按预期工作并且其语义等价物爆炸似乎对我很诡异。这可能是在规范?我试图很难找到一种方法,这不会在编译器或运行时造成问题,也不会有任何问题。

这是一个简化的例子,它重现了这个问题,并且只使用了核心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);



$ b你的假设是正确的,特定于JRE的实现将目标方法作为 MethodHandle

与许多通用结构类似,字节代码级别上需要类型转换, t出现在源代码中。由于 LambdaMetafactory 显式需要一个直接方法句柄,封装这种类型转换的方法引用不能作为 MethodHandle 传递给工厂。 / p>

有两种可能的方式来处理它。

第一种解决方案是将<$ c $如果接收者类型是接口,并且将 c> LambdaMetafactory 信任 MethodHandle 在生成的lambda类中自行生成所需的类型,而不是拒绝它。毕竟,它对于参数和返回类型已经是类似的了。



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



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


I have some code with a method reference that compiles fine and fails at runtime.

The exception is so:

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)

The class is like so:

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

The exception is thrown trying to resolve entity::filename. filename() is declared on HasImagesEntity. Near as I can tell, I get the exception because the erasure of E is BasicEntity and the JVM doesn't (can't?) consider other bounds on E.

When I rewrite the method reference as a trivial lambda, everything is fine. It seems really fishy to me that one construct works as expected and its semantic equivalent blows up. Could this possibly be in the spec? I'm trying very hard to find a way for this not to be a problem in the compiler or runtime, and haven't come up with anything.

解决方案

Here is a simplified example which reproduces the problem and uses only core Java classes:

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);
}

Your assumption is correct, the JRE-specific implementation receives the target method as a MethodHandle which has no information about generic types. Therefore the only thing it sees is that the raw types mismatch.

Like with a lot of generic constructs, there is a type cast required on the byte code level which doesn’t appear in the source code. Since LambdaMetafactory explicitly requires a direct method handle, a method reference which encapsulates such a type cast cannot be passed as a MethodHandle to the factory.

There are two possible ways to deal with it.

First solution would be to change the LambdaMetafactory to trust the MethodHandle if the receiver type is an interface and insert the required type cast by itself in the generated lambda class instead of rejecting it. After all, it does similar for parameter and return types already.

Alternatively, the compiler would be in charge to create a synthetic helper method encapsulating the type cast and method call, just like if you had written a lambda expression. This is not a unique situation. If you use a method reference to a varargs method or an array creation like, e.g. String[]::new, they can’t be expressed as direct method handles and end up in synthetic helper methods.

In either case, we can consider the current behavior a bug. But obviously, compiler and JRE developers must agree on which way it should be handled before we can say on which side the bug resides.

这篇关于带泛型的LambdaConversionException:JVM错误?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆