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