转换Java功能接口

时间:2017-05-31 15:27:19

标签: java lambda java-8 classcastexception functional-interface

一如既往,我正在查看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)

找到此代码

3 个答案:

答案 0 :(得分:22)

让我们看看下面的代码,然后你就会明白为什么?

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

任何类都可以实现多接口,一个是Consumer<Integer>可能实现另一个是IntConsumer。有时候我们想要将IntConsumer调整为Consumer<Integer>并保存其原始类型(IntConsumer),然后代码如下所示:

class IntConsumerAdapter implements Consumer<Integer>, IntConsumer {

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

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

注意:它是Class Adapter Design Pattern的用法。

那么您可以将IntConsumerAdapter用作Consumer<Integer>IntConsumer,例如:

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

Sink.OfInt是jdk-8中Class Adapter Design Pattern的具体用法。Sink.OfInt#accept(Integer)的缺点显然是JVM在接受{{1}时会抛出NullPointerException }},这就是null 可见的原因。

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 }

我发现为什么需要将Sink投放到Consumer<Integer>,如果传递像IntConsumer这样的消费者?

一个原因是当我们使用IntConsumerAdapter接受Consumer时,编译器需要将其自动装箱到int。在方法Integer中,您需要手动将accept(Integer)打包到Integer。 换句话说,每个int为装箱/拆箱做了2个额外的操作。它需要提高性能,以便在算法库中进行一些特殊的检查。

另一个原因是重复使用一段代码。 OfInt#forEachRemaining(Consumer)的正文是应用Adapter Design Pattern重新使用OfInt#forEachRenaming(IntConsumer)的一个很好的例子。

accept(Integer)

答案 1 :(得分:21)

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

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

来自Java语言规范,5.5.1: Reference Type Casting

  

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

     

...

     

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

     

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

答案 2 :(得分:10)

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

  

实施要求:

     

如果操作是IntConsumer的实例,则会将其转换为IntConsumer并传递给forEachRemaining(java.util.function.IntConsumer);否则,通过装箱IntConsumer的参数,将操作调整为IntConsumer的实例,然后传递给forEachRemaining(java.util.function.IntConsumer)

因此,在任何一种情况下,都会调用forEachRemaining(IntConsumer),这是实际的实现方法。但是如果可能的话,将省略拳击适配器的创建。原因是Spliterator.OfInt也是Spliterator<Integer>,只提供forEachRemaining(Consumer<Integer>)方法。特殊行为允许同等地处理通用Spliterator实例及其原始(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));
}
Stream.of(1, 2, 3):
unboxing 1
1
unboxing 2
2
unboxing 3
3
IntStream.range(0, 3)
0
1
2