奇怪的异常“无效的接收器类型类 java.lang.Object;不是……的子类型"

2022-01-22 00:00:00 java-8 java java-stream

我在使用 jre1.8.0_66 运行的代码中遇到了这个奇怪的异常:

I'm getting this strange exception in code run using jre1.8.0_66:

Exception in thread "main" java.lang.BootstrapMethodError: call site initialization exception
    at java.lang.invoke.CallSite.makeSite(CallSite.java:341)
    at java.lang.invoke.MethodHandleNatives.linkCallSiteImpl(MethodHandleNatives.java:307)
    at java.lang.invoke.MethodHandleNatives.linkCallSite(MethodHandleNatives.java:297)
    at main
Caused by: java.lang.invoke.LambdaConversionException: Invalid receiver type class java.lang.Object; not a subtype of implementation type interface Fruit
    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:302)
    ... 3 more

这是什么意思?代码如下:

What's it mean? The code is as follows:

public static interface Fruit {

    int getPickingMonth();
}

public static class Apple implements Fruit, Serializable {

    @Override
    public int getPickingMonth() {
        return 11;
    }
}

public static class Orange implements Fruit, Serializable {

    @Override
    public int getPickingMonth() {
        return 2;
    }
}

public static void main(String[] args) {

    List<Apple> apples = Arrays.asList(new Apple());
    List<Orange> oranges = Arrays.asList(new Orange());

    Stream.of(apples.stream(), oranges.stream())
            .flatMap(Function.identity())
            .map(Fruit::getPickingMonth)  // exception occurs on this line
            .forEachOrdered(System.out::println);
}

如果我将 Fruit::getPickingMonth 更改为 x ->,异常就会消失.x.getPickingMonth().

The exception goes away if I change Fruit::getPickingMonth to x -> x.getPickingMonth().

值得:如果我从任一类中删除 Serializable,异常也会消失.但是,如果我向两个类添加另一个等效接口,则返回,例如Cloneable 或者一些自定义界面.

For what it's worth: The exception also goes away if I remove Serializable from either class. But returns if I add another, equivalent interface to both classes, e.g. Cloneable or some custom interface.

推荐答案

您遇到了在 this 中讨论过的相同编译器错误问题和那个问题.

只要涉及交集类型,并且您使用的方法引用使用的接收器类型不是第一个类型(第一个类型是类型擦除后将保留的类型),就会出现问题.

The problem occurs whenever an intersection type is involved and you are using a method reference using a receiver type other than the first one (the first type is the one that will remain after type erasure).

因此,当您将方法引用替换为 lambda 表达式时,您将不再受该错误的影响.如果您从类型中删除 Serializable,则 Stream 的推断元素类型将是 Fruit,即不是交集类型,并且再次问题不会发生.但是对于实现 FruitSerializable 的两种元素类型,编译器将推断元素类型 Object&Fruit&Serializable 并且原始类型将是Object 在使用接收器类型 Fruit 的方法引用时引发错误.您可以轻松解决此问题:

So when you replace the method reference with a lambda expression, you are not affected by the bug anymore. If you remove the Serializable from the types instead, the inferred element type of the Stream will be Fruit, i.e. not an intersection type, and again the problem does not occur. But with the two element types implementing Fruit and Serializable, the compiler will infer the element type Object&Fruit&Serializable and the raw type will be Object which provokes the error when using a method reference with the receiver type Fruit. You can easily work around this:

Stream.of(apples.stream(), oranges.stream())
        .<Fruit>flatMap(Function.identity())
        .map(Fruit::getPickingMonth) // no more exception on this line
        .forEachOrdered(System.out::println);

编译后的代码将与您的原始代码相同,但 flatMap 操作的正式结果类型将是 Stream,忽略推断的所有其他工件交叉点类型.因此,方法引用 Fruit::getPickingMonth 将实现类型 Function 而不是 Function 并且编译器错误没有实现.

The compiled code will be identical to your original, but the formal result type of the flatMap operation will be Stream<Fruit>, ignoring all other artifacts of the inferred intersection type. As a consequence the method reference Fruit::getPickingMonth will implement the type Function<Fruit,Integer> instead of Function<Object&Fruit&Serializable,Integer> and the compiler bug does not materialize.

但请注意,您的代码过于复杂.你可以简单地使用

But note that your code is unnecessarily complicated. You can simply use

Stream.<Fruit>concat(apples.stream(), oranges.stream())
        .map(Fruit::getPickingMonth) // no more exception on this line
        .forEachOrdered(System.out::println);

达到同样的目的.

相关文章