转换 Java 功能接口

像往常一样,我正在查看JDK 8源代码,发现了非常有趣的代码:

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

问题是:怎么可能是一个实例?因为它们处于不同的层次结构中。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);
    }
}

但它抛出了:ClassCastException

Exception in thread "main" 
    java.lang.ClassCastException: 
       com.example.InterfaceExample$$Lambda$1/764977973 
     cannot be cast to 
       java.util.function.Consumer

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


答案 1

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

class IntegerConsumer implements Consumer<Integer>, IntConsumer {
   ...
}

任何类都可以实现多接口,一个是实现另一个是。有时当我们想要适应并保存其原始类型()时,会发生这种情况,然后代码如下所示:Consumer<Integer>IntConsumerIntConsumerConsumer<Integer>IntConsumer

class IntConsumerAdapter implements Consumer<Integer>, IntConsumer {

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

    @Override
    public void accept(int value) {
        // todo
    }
}

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

然后,您可以同时使用 as 和 ,例如:IntConsumerAdapterConsumer<Integer>IntConsumer

Consumer<? extends Integer> consumer1 = new IntConsumerAdapter();
IntConsumer consumer2 = new IntConsumerAdapter();

Sink.OfInt是 jdk-8 中类适配器设计模式的具体用法。缺点显然是JVM在接受值时会抛出一个,所以这就是为什么是可见的。Sink.OfInt#accept(Integer)NullPointerExceptionnullSink

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>IntConsumerIntConsumerAdapter

一个原因是当我们使用 a 来接受一个编译器需要自动将其装箱到 .在您需要手动拆箱的方法中。换句话说,每个操作都执行 2 个额外的装箱/拆箱操作。它需要提高性能,因此它会在算法库中进行一些特殊检查。ConsumerintIntegeraccept(Integer)Integerintaccept(Integer)

另一个原因是重用一段代码。OfInt#forEachRemaining(Consumer) 的主体是应用 Adapter Design Pattern 重用 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);
    }
}

答案 2

因为实现类可能同时实现这两个接口。

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

来自 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 的子类也可能实现)。


推荐