转换 Java 个功能接口
Casting Java functional interfaces
我一如既往地浏览了 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/764977973
cannot be cast to
java.util.function.Consumer
您可以在 java.util.Spliterator.OfInt#forEachRemaining(java.util.function.Consumer)
找到此代码
我们看看下面的代码,你就知道为什么了吗?
class IntegerConsumer implements Consumer<Integer>, IntConsumer {
...
}
任何 class 都可以实现多接口,一个是 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的用法。
THEN 您可以将 IntConsumerAdapter
用作 Consumer<Integer>
和 IntConsumer
,例如:
Consumer<? extends Integer> consumer1 = new IntConsumerAdapter();
IntConsumer consumer2 = new IntConsumerAdapter();
Sink.OfInt
是 Class Adapter Design Pattern 在 jdk-8.The 中的具体用法 Sink.OfInt#accept(Integer)
的缺点显然是 JVM 会抛出 NullPointerException
当它接受 null
值时,这就是 Sink
是 package 可见的原因。
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 }
我发现如果像 IntConsumerAdapter
这样的消费者传递,为什么需要将 Consumer<Integer>
转换为 IntConsumer
?
一个原因是当我们使用 Consumer
接受 int
时,编译器需要将其自动装箱为 Integer
。在方法 accept(Integer)
中,您需要手动将 Integer
拆箱为 int
。
换句话说,每个 accept(Integer)
为 boxing/unboxing 做了 2 个额外的操作。它需要提高性能,所以它在算法库中做了一些特殊的检查。
另一个原因是重复使用一段代码。 OfInt#forEachRemaining(Consumer) is a good example of applying Adapter Design Pattern for reusing 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);
}
}
因为实现 class 可能会同时实现这两个接口。
将任何类型转换为任何接口类型都是合法的,只要传递的对象可能实现目标接口。当源类型是未实现接口的最终 class 或可以证明具有不同类型参数化时,这在 编译时 已知为假这导致相同的擦除。在运行-时间,如果对象没有实现接口,你会得到一个ClassCastException
。在尝试转换之前检查 instanceof
可以避免异常。
来自 Java 语言规范,5.5.1: Reference Type Casting:
5.5.1 Reference Type Casting
Given a compile-time reference type S (source) and a compile-time reference type
T (target), a casting conversion exists from S to T if no compile-time errors occur
due to the following rules.
...
• If T is an interface type:
– If S is not a final class (§8.1.1), then, if there exists a supertype X of T, and a supertype Y of S, such that both X and Y are provably distinct parameterized types, and that the erasures of X and Y are the same, a compile-time error occurs.
Otherwise, the cast is always legal at compile time (because even if S does not implement T, a subclass of S might).
请注意,您可以在不查看源代码的情况下发现此行为,只需查看 the official API documentation,您就已经链接了自己:
Implementation Requirements:
If the action is an instance of IntConsumer
then it is cast to IntConsumer
and passed to forEachRemaining(java.util.function.IntConsumer); otherwise the action is adapted to an instance of IntConsumer
, by boxing the argument of IntConsumer
, and then passed to forEachRemaining(java.util.function.IntConsumer).
所以无论哪种情况,都会调用forEachRemaining(IntConsumer)
,这才是实际的实现方式。但如果可能,将省略装箱适配器的创建。原因是Spliterator.OfInt
也是Spliterator<Integer>
,只提供forEachRemaining(Consumer<Integer>)
方法。特殊行为允许平等对待通用 Spliterator
实例及其原始 (Spliterator.OfPrimitive
) 实例,并自动选择最有效的方法。
正如其他人所说,您可以用普通的class实现多个接口。此外,如果创建辅助类型,则可以使用 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
我一如既往地浏览了 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/764977973
cannot be cast to
java.util.function.Consumer
您可以在 java.util.Spliterator.OfInt#forEachRemaining(java.util.function.Consumer)
找到此代码我们看看下面的代码,你就知道为什么了吗?
class IntegerConsumer implements Consumer<Integer>, IntConsumer {
...
}
任何 class 都可以实现多接口,一个是 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的用法。
THEN 您可以将 IntConsumerAdapter
用作 Consumer<Integer>
和 IntConsumer
,例如:
Consumer<? extends Integer> consumer1 = new IntConsumerAdapter();
IntConsumer consumer2 = new IntConsumerAdapter();
Sink.OfInt
是 Class Adapter Design Pattern 在 jdk-8.The 中的具体用法 Sink.OfInt#accept(Integer)
的缺点显然是 JVM 会抛出 NullPointerException
当它接受 null
值时,这就是 Sink
是 package 可见的原因。
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 }
我发现如果像 IntConsumerAdapter
这样的消费者传递,为什么需要将 Consumer<Integer>
转换为 IntConsumer
?
一个原因是当我们使用 Consumer
接受 int
时,编译器需要将其自动装箱为 Integer
。在方法 accept(Integer)
中,您需要手动将 Integer
拆箱为 int
。
换句话说,每个 accept(Integer)
为 boxing/unboxing 做了 2 个额外的操作。它需要提高性能,所以它在算法库中做了一些特殊的检查。
另一个原因是重复使用一段代码。 OfInt#forEachRemaining(Consumer) is a good example of applying Adapter Design Pattern for reusing 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);
}
}
因为实现 class 可能会同时实现这两个接口。
将任何类型转换为任何接口类型都是合法的,只要传递的对象可能实现目标接口。当源类型是未实现接口的最终 class 或可以证明具有不同类型参数化时,这在 编译时 已知为假这导致相同的擦除。在运行-时间,如果对象没有实现接口,你会得到一个ClassCastException
。在尝试转换之前检查 instanceof
可以避免异常。
来自 Java 语言规范,5.5.1: Reference Type Casting:
5.5.1 Reference Type Casting Given a compile-time reference type S (source) and a compile-time reference type T (target), a casting conversion exists from S to T if no compile-time errors occur due to the following rules.
...
• If T is an interface type: – If S is not a final class (§8.1.1), then, if there exists a supertype X of T, and a supertype Y of S, such that both X and Y are provably distinct parameterized types, and that the erasures of X and Y are the same, a compile-time error occurs.
Otherwise, the cast is always legal at compile time (because even if S does not implement T, a subclass of S might).
请注意,您可以在不查看源代码的情况下发现此行为,只需查看 the official API documentation,您就已经链接了自己:
Implementation Requirements:
If the action is an instance of
IntConsumer
then it is cast toIntConsumer
and passed to forEachRemaining(java.util.function.IntConsumer); otherwise the action is adapted to an instance ofIntConsumer
, by boxing the argument ofIntConsumer
, and then passed to forEachRemaining(java.util.function.IntConsumer).
所以无论哪种情况,都会调用forEachRemaining(IntConsumer)
,这才是实际的实现方式。但如果可能,将省略装箱适配器的创建。原因是Spliterator.OfInt
也是Spliterator<Integer>
,只提供forEachRemaining(Consumer<Integer>)
方法。特殊行为允许平等对待通用 Spliterator
实例及其原始 (Spliterator.OfPrimitive
) 实例,并自动选择最有效的方法。
正如其他人所说,您可以用普通的class实现多个接口。此外,如果创建辅助类型,则可以使用 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