ポリモーフィズムクイズ

問題

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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関数が利用され、二行目の出力結果と同じようになります。

参考