带泛型的LambdaConversionException:JVM错误?

Ste*_*Kay 21 java generics lambda java-8

我有一些带有方法引用的代码,它可以很好地编译并在运行时失败.

例外是这样的:

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

触发异常的类:

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

抛出异常试图解决entity::filename.filename()宣布在HasImagesEntity.据我所知,我得到了异常,因为E的删除是BasicEntity,JVM没有(不能?)考虑E上的其他界限.

当我将方法引用重写为一个普通的lambda时,一切都很好.对我来说,一个构造按预期工作并且它的语义等价物爆炸似乎真的很可疑.

这可能是在规范中吗?我正在努力寻找一种不会在编译器或运行时出现问题的方法,并且没有提出任何建议.

Hol*_*ger 21

这是一个简化的示例,它可以重现问题并仅使用核心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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

有两种可能的方法来处理它.

第一种解决方案是更改LambdaMetafactory为信任MethodHandle接收器类型是否为a interface并在生成的lambda类中插入所需的类型,而不是拒绝它.毕竟,它已经类似于参数和返回类型.

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

在任何一种情况下,我们都可以将当前行为视为一个错误.但很明显,编译器和JRE开发人员必须就应该如何处理之前达成一致,然后我们才能说出bug存在于哪一方面.


小智 15

我刚刚在JDK9和JDK8u45中解决了这个问题.看到这个bug.这种变化需要一段时间才能渗透到推广的构建中.Dan刚刚在StackOverflow问题上指出了我,所以我添加了这个注释.当你发现bug时,请提交它们.

我通过让编译器创建一个桥来解决这个问题,这是许多复杂方法引用的方法.我们也在研究规范含义.

  • 这个错误没有修复,请查看:/sf/ask/3313753201/并查看评论,谢谢. (4认同)
  • 仍然似乎有1.8.0_51,[这](http://stackoverflow.com/q/31711967/1093528)问题似乎是完全相同的问题... (3认同)
  • @fge:对,即使这里我的答案的示例代码仍然展示了bug,唯一已修复的案例是bug报告中的示例代码... (3认同)

Mat*_*aun 12

这个bug并没有完全修复.我刚刚LambdaConversionException在1.8.0_72中遇到了一个问题,看到Oracle的bug跟踪系统中有开放的bug报告: link1,link2.

(编辑:报告链接的错误在JDK 9 b93中关闭)

作为一个简单的解决方法,我避免方法句柄.而不是

.map(entity::filename)
Run Code Online (Sandbox Code Playgroud)

我做

.map(entity -> entity.filename())
Run Code Online (Sandbox Code Playgroud)

这是在Debian 3.11.8-1 x86_64上重现问题的代码.

import java.awt.Component;
import java.util.Collection;
import java.util.Collections;

public class MethodHandleTest {
    public static void main(String... args) {
        new MethodHandleTest().run();
    }

    private void run() {
        ComponentWithSomeMethod myComp = new ComponentWithSomeMethod();
        new Caller<ComponentWithSomeMethod>().callSomeMethod(Collections.singletonList(myComp));
    }

    private interface HasSomeMethod {
        void someMethod();
    }

    static class ComponentWithSomeMethod extends Component implements HasSomeMethod {
        @Override
        public void someMethod() {
            System.out.println("Some method");
        }
    }

    class Caller<T extends Component & HasSomeMethod> {
        public void callSomeMethod(Collection<T> components) {
            components.forEach(HasSomeMethod::someMethod); //  <-- crashes
//          components.forEach(comp -> comp.someMethod());     <-- works fine

        }
    }
}
Run Code Online (Sandbox Code Playgroud)