ポリモーフィズムクイズ

  • 4
    いいね
  • 0
    コメント

Java Puzzlers Advent Calendar 2016の投稿は一つだけ足りないという惜しい状況になっていますので、Day 5の記事を代打させて頂きます。

問題

class MyTest
{
    private static class A {
        String message = "hello";
        Consumer<String> hello = s -> System.out.print(message + s);
        void hello(String s) { this.hello.accept(s); }
    }

    private static class B extends A {
        String message = "ハロー";
        void hello(String s) { this.hello.accept(s); }
    }

    public static void main(String[] args) {
        A b = (A) new B();
        b.hello(" world");
        System.out.print("/");
        b.hello.accept(" world");
    }
}

上記のコードの実行結果は

  • A. ハロー world/hello world
  • B. ハロー world/ハロー world
  • C. hello world/ハロー world
  • D. hello world/hello world
  • E. コンパイルエラー

正解

答えは

  • D. hello world/hello world

説明

一見複雑そうですが、実はポリモーフィズムをちゃんと理解できれば、すぐ解けるはずです。それでは、mainメソッドの処理を追って見ましょう。

- mainメソッドの一行目ではAB実装の変数bを宣言しました。(A)というアップキャストの処理が書かれていますが、実はコンパイラが自動的に行ってくれる作業なので、ただの目障りで意味がありません(勿論エラーにもなりませんが・・)。
- bのメソッドを利用する際に、コンパイラはまず実装型にそのメソッドがあるかどうかを見ます(二行目)。クラスBhelloというメソッドがありましたね。オーバーライドが明記されていませんが、シグネチャに違いがない為、親クラスのhelloメソッドがオーバーライドされていることは分かります。ポリモーフィズムのルールによりクラスBhelloメソッドが実行されます。
- helloいうメソッドはhelloというコンシューマー関数のフィールドを呼んでいますが、残念ながらAクラスにしかhelloが存在しません。その為、やむを得ずAクラス内のhello関数を利用することになります。(ちなみに、メソッド名とフィールド名の重複は許されますので、コンパイルエラーになりません)
- helloという関数はmessageというフィールドを使います。クラスAmessagehelloですので、出力結果はhello worldになります。クラスBにもmessageというフィールドが存在しますが、今使っているhello関数とは無関係ですね。
- さらにmainの四行目ですが、bオブジェクトのhello関数を利用しているように見えますね。しかし、フィールド変数のオーバーライドが存在しないことを忘れてはいけません。hello関数はhelloメソッドと同じく処理手続きを定義するものですが、本質的にはmessageのようなフィールド変数です。その為、どのフィールドが利用されるかは宣言時の参照型によります。Aとして宣言された為、クラスAhello関数が利用され、二行目の出力結果と同じようになります。

参考

この投稿は Java Puzzlers Advent Calendar 20165日目の記事です。