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()
を呼べない。