转换Java功能接口

And*_*mov 25 java lambda classcastexception java-8 functional-interface

一如既往,我正在查看JDK 8源代码并发现非常有趣的代码:

@Override
default void forEachRemaining(Consumer<? super Integer> action) {
    if (action instanceof IntConsumer) {
        forEachRemaining((IntConsumer) action);
    } 
}
Run Code Online (Sandbox Code Playgroud)

问题是:怎么Consumer<? super Integer>可能是一个实例 IntConsumer?因为他们处于不同的等级.

我已经制作了类似的代码片段来测试:

public class InterfaceExample {
    public static void main(String[] args) {
        IntConsumer intConsumer = i -> { };
        Consumer<Integer> a = (Consumer<Integer>) intConsumer;

        a.accept(123);
    }
}
Run Code Online (Sandbox Code Playgroud)

但它抛出ClassCastException:

Exception in thread "main" 
    java.lang.ClassCastException: 
       com.example.InterfaceExample$$Lambda$1/764977973 
     cannot be cast to 
       java.util.function.Consumer
Run Code Online (Sandbox Code Playgroud)

你可以在java.util.Spliterator.OfInt#forEachRemaining(java.util.function.Consumer)找到这段代码.

hol*_*ava 22

让我们看看下面的代码,然后你可以看到为什么?

class IntegerConsumer implements Consumer<Integer>, IntConsumer {
   ...
}
Run Code Online (Sandbox Code Playgroud)

任何类都可以实现多接口,一个Consumer<Integer>可能实现另一个接口IntConsumer.有时会发生,当我们要调整IntConsumerConsumer<Integer>和保存其成因类型(IntConsumer),那么代码看起来如下:

class IntConsumerAdapter implements Consumer<Integer>, IntConsumer {

    @Override
    public void accept(Integer value) {
        accept(value.intValue());
    }

    @Override
    public void accept(int value) {
        // todo
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:这是类适配器设计模式的用法.

那么你可以使用IntConsumerAdapter既作为Consumer<Integer>IntConsumer,例如:

Consumer<? extends Integer> consumer1 = new IntConsumerAdapter();
IntConsumer consumer2 = new IntConsumerAdapter();
Run Code Online (Sandbox Code Playgroud)

Sink.OfInt是的具体使用类适配器设计模式在JDK-8,下跌空间Sink.OfInt#accept(Integer)显然是JVM将抛出NullPointerException时,它接受一个null值,所以这就是为什么Sink可见.

189  interface OfInt extends Sink<Integer>, IntConsumer {
190 @Override
191 void accept(int value);
193 @Override
194 default void accept(Integer i) {
195 if (Tripwire.ENABLED)
196 Tripwire.trip(getClass(), "{0} calling Sink.OfInt.accept(Integer)");
197 accept(i.intValue());
198 }
199 }

我发现它为什么要投了Consumer<Integer>一个IntConsumer,如果通过一个消费者喜欢的IntConsumerAdapter

一个原因是当我们使用a Consumer来接受int编译器需要将其自动装箱时Integer.而在方法accept(Integer),你需要拆箱一Integerint手动.换句话说,每个accept(Integer)都进行2次额外的装箱/拆箱操作.它需要提高性能,以便在算法库中进行一些特殊检查.

另一个原因是重用一段代码.OfInt#forEachRemaining(Consumer)的主体是应用适配器设计模式以重用OfInt#forEachRenaming(IntConsumer)的一个很好的例子 .

default void forEachRemaining(Consumer<? super Integer> action) {
    if (action instanceof IntConsumer) {
    //   action's implementation is an example of Class Adapter Design Pattern
    //                                   |
        forEachRemaining((IntConsumer) action);
    }
    else {
    //  method reference expression is an example of Object Adapter Design Pattern
    //                                        |
        forEachRemaining((IntConsumer) action::accept);
    }
}
Run Code Online (Sandbox Code Playgroud)


And*_*mas 21

因为实现类可能实现两个接口.

将任何类型转换为任何接口类型是合法的,只要传递的对象可能实现目标接口即可.当源类型是未实现接口的最终类​​,或者可以证明它具有导致相同擦除的不同类型参数化时,这在编译时是已知的.在运行时,如果对象没有实现接口,你会得到一个ClassCastException.instanceof在尝试强制转换之前进行检查可以避免异常.

从Java语言规范,5.5.1:引用类型转换:

5.5.1引用类型转换给定编译时引用类型S(源)和编译时引用类型T(目标),如果由于以下规则而没有发生编译时错误,则从S到T存在转换转换.

...

•如果T是一个接口类型: - 如果S不是最终类(§8.1.1),那么,如果存在T的超类型X和S的超类型Y,那么X和Y都可证明是不同的参数化类型,并且X和Y的擦除相同,发生编译时错误.

否则,强制转换在编译时总是合法的(因为即使S没有实现T,S的子类也可能).

  • @AndriiAbramov,由推断类型`Consumer <?的lambda表达式产生的对象 super Integer>`不会是`IntConsumer`的实例.在这种情况下,您提供的方法中的`instanceof`表达式将计算为"false",结果是该方法没有可观察到的影响. (5认同)
  • 是的,其中一个关键点是功能接口类型的对象不一定对应于lambda表达式. (2认同)

Hol*_*ger 10

请注意,您可以在不查看源代码的情况下找到此行为,只需查看官方API文档,您已自行链接:

实施要求:

如果该动作是一个实例,IntConsumer那么它将被强制转换IntConsumer并传递给forEachRemaining(java.util.function.IntConsumer) ; 否则该动作适用于IntConsumer拳击参数的一个实例IntConsumer,然后传递给forEachRemaining(java.util.function.IntConsumer).

因此在任何一种情况下,forEachRemaining(IntConsumer)都会被调用,这是实际的实现方法.但是如果可能的话,将省略拳击适配器的创建.原因是a Spliterator.OfInt也是a Spliterator<Integer>,它只提供forEachRemaining(Consumer<Integer>)方法.特殊行为允许同等地处理泛型Spliterator实例及其primitive(Spliterator.OfPrimitive)对应项,并自动选择最有效的方法.

正如其他人所说,您可以使用普通类实现多个接口.此外,如果创建辅助类型,则可以使用lambda表达式实现多个接口,例如

interface UnboxingConsumer extends IntConsumer, Consumer<Integer> {
    public default void accept(Integer t) {
        System.out.println("unboxing "+t);
        accept(t.intValue());
    }
}
public static void printAll(BaseStream<Integer,?> stream) {
    stream.spliterator().forEachRemaining((UnboxingConsumer)System.out::println);
}
public static void main(String[] args) {
    System.out.println("Stream.of(1, 2, 3):");
    printAll(Stream.of(1, 2, 3));
    System.out.println("IntStream.range(0, 3)");
    printAll(IntStream.range(0, 3));
}
Run Code Online (Sandbox Code Playgroud)
Stream.of(1, 2, 3):
unboxing 1
1
unboxing 2
2
unboxing 3
3
IntStream.range(0, 3)
0
1
2
Run Code Online (Sandbox Code Playgroud)

  • @ holi-java:多么巧合,你刚刚添加到答案中的JRE的`Sink.OfInt`是"UnboxingConsumer"的真实例子. (4认同)