Java

ポリモーフィズムクイズ

More than 1 year has passed since last update.

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


参考