public interface A {
void test();
}
public class B implements A {
public void test() {
System.out.println("B");
}
public void specificMethod() {
System.out.println("Specific to B");
}
}
public class Main {
public static void main(String[] args) {
A a = new B();
a.test(); // OK: 実行結果は"B"
a.specificMethod(); // NG: コンパイルエラー
}
}
早速ですが、上の結果に違和感はありませんか?
ないならここから先は読む必要がありません。
これがあやふやになってる人は読んでください。
なぜA型なのにBクラスのtest()が呼ばれているの?
Javaにおいて、変数の型(コンパイル時の型) と 実際のインスタンス(実行時の型) は区別されます。今回の例では変数 a は A 型ですが、実際のインスタンスは new B() で作られているため、JVMは実行時の型 である B の test() メソッドを呼び出します。
このように、オーバーライドされたメソッド呼び出しは実行時型を基準に決まる(動的バインディング) ため、A の test() ではなく B の test() が呼ばれるというわけです。
A a = new B();
a.test(); // 実行時にBクラスのtest()が呼ばれる
どうしてコンパイル時と実行時で違いが出るの?
コンパイル時:
変数
aはA型として扱われるため、「aから呼び出せるメソッド =Aに定義されているメソッド」と判断される。
実行時:
実際には
new B()で作成されたインスタンスなので、「呼び出すメソッド本体 =Bでオーバーライドしたtest()」が使われる。
Javaのメソッド呼び出しは基本的に「変数の型」ではなく「実行時のオブジェクト型」をもとにどのメソッドを呼ぶかが決定されます。これが「動的バインディング(ポリモーフィズム)」というやつらしいです。
なぜnew B()してるのにBクラスのspecificMethod()を呼べないの?
一方で、メソッドが呼び出せるかどうか は コンパイル時 に「この変数の型が何か」によって決まります。変数 a は A 型なので、コンパイラが「A に定義されていないメソッドを呼び出してるやんけ
」と判断し、コンパイルエラー となります。
A a = new B();
a.specificMethod(); // コンパイルエラー: AにはspecificMethod()がない
じゃあ呼びたい場合はどうするの?
B クラスのメソッドを呼びたいのであれば、以下のように変数の型を B にするか、キャスト によって B 型であることを保証する必要があります。
// 方法1. 変数をB型にする
B b = new B();
b.specificMethod(); // OK
// 方法2. キャストを使う
A a = new B();
((B)a).specificMethod(); // OK (実行時に本当にB型か確認される)
実際には a が B 型のインスタンスであるときだけ (B)a は成功します。
もし他の実装クラス(例: Cクラス)だった場合は ClassCastException が発生します。
まとめ
⇒ だから A 型変数でも B の test() が呼ばれる。
⇒ だから A 型変数では A インターフェースにないメソッド specificMethod()を呼べない。