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関数が利用され、二行目の出力結果と同じようになります。