web-dev-qa-db-ja.com

キャスト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) にあります。

26
Andrii Abramov

以下のコードを見てみましょう。その理由がわかりますか?

_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値を受け入れるため、Sinkpackageが表示されます。

189  インターフェース OfInt 伸びる  シンク  < 整数 >、 IntConsumer  {
19 @ オーバーライド
191  ボイド 受け入れる(int 値);
19 @ オーバーライド
194  defaultボイド accept( 整数 i){
195  もし (Tripwire.ENABLED)
196 Tripwire . tripgetClass ()、 "{0} calling Sink.OfInt.accept(Integer)");
197accept (i . intValue ());
198 }
199 }

IntConsumerのようなコンシューマを渡す場合、_Consumer<Integer>_をIntConsumerAdapterにキャストする必要があるのはなぜですか。

1つの理由は、Consumerを使用してintを受け入れるときに、コンパイラがそれをIntegerに自動ボックス化する必要があるためです。そして、メソッドaccept(Integer)では、Integerintに手動で開梱する必要があります。つまり、各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);
    }
}
_
22
holi-java

実装クラスは両方のインターフェースを実装する可能性があるためです。

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のサブクラスが可能性があるためです)。

21
Andy Thomas

公式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
_
10
Holger