いつものように、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) にあります。
以下のコードを見てみましょう。その理由がわかりますか?
_class IntegerConsumer implements Consumer<Integer>, IntConsumer {
...
}
_
どのクラスでもマルチインターフェイスを実装できます。1つは_Consumer<Integer>
_で、もう1つはIntConsumer
で実装されます。 IntConsumer
を_Consumer<Integer>
_に適合させ、そのOriginタイプ(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がNullPointerException
をスローすることです。 null
値を受け入れるため、Sink
はpackageが表示されます。
189 インターフェース OfInt 伸びる シンク < 整数 >、 IntConsumer {
19 @ オーバーライド
191 ボイド 受け入れる(int 値);
19 @ オーバーライド
194 defaultボイド accept( 整数 i){
195 もし (Tripwire.ENABLED)
196 Tripwire . trip ( getClass ()、 "{0} calling Sink.OfInt.accept(Integer)");
197accept (i . intValue ());
198 }
199 }
IntConsumer
のようなコンシューマを渡す場合、_Consumer<Integer>
_をIntConsumerAdapter
にキャストする必要があるのはなぜですか。
1つの理由は、Consumer
を使用してint
を受け入れるときに、コンパイラがそれをInteger
に自動ボックス化する必要があるためです。そして、メソッドaccept(Integer)
では、Integer
をint
に手動で開梱する必要があります。つまり、各accept(Integer)
は、ボックス化/ボックス化解除のために2つの追加操作を実行します。アルゴリズムライブラリで特別なチェックを行うため、パフォーマンスを改善する必要があります。
別の理由は、コードの一部を再利用することです。 OfInt#forEachRemaining(Consumer) の本文は、 OfInt#forEachRenaming(IntConsumer) を再利用するために Adapter Design Pattern を適用する良い例です。
_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);
}
}
_
実装クラスは両方のインターフェースを実装する可能性があるためです。
objectが渡される限り、任意のタイプを任意のインターフェースタイプにキャストすることは合法ですmight宛先インターフェースを実装します。これは、compile-timeで、ソースタイプがインターフェースを実装しない最終クラスである場合、または異なるタイプのパラメーター化が原因であることが判明した場合にfalseであることがわかっています。同じ消去で。 run-timeで、オブジェクトがインターフェースを実装していない場合、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のサブクラスが可能性があるためです)。
公式APIドキュメント を確認するだけで、ソースコードを調べなくてもこの動作が見つかる可能性があることに注意してください。
実装要件:
アクションが
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
_)を同等に扱うことができます。
他の人が言ったように、通常のクラスで複数のインターフェースを実装できます。また、ヘルパータイプを作成する場合は、ラムダ式を使用して複数のインターフェースを実装できます。
_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
_