第 2 章 Java 言語の基本的な文法 I 5
3.43 OverrideExample3.java ( クラスのキャスト )
public class OverrideExample3 {
public static void main(String[] args) { SubC e = new SubC();
System.out.println( "c=" + e.c );
// System.out.println( ((SuperC) e).c );
e.print();
// ((SuperC) e).print();
e.print2();
// ((SuperC) e).print2();
} }
class SuperC { int c = 1;
void print() {
System.out.println( "SuperC.print()" );
} }
class SubC extends SuperC { int c = 2;
@Override void print() {
System.out.println( "SubC.print()" );
}
void print2() {
System.out.println( "SubC.print2()" );
} }
このプログラムが何をしようとしているか読み解きましょう。クラス SubCのインスタンス eを作り、そのフィール ドcと2つのメソッド print()とprint2()を動かしています。print()メソッドがオーバーライドされているの は、チェックOKですか?
では、3つのコメント部分の//を外してみて下さい。この部分に書かれているのが、クラスの「キャスト」です。
インスタンス eにキャスト(SuperC)を付けると、それはスーパークラス SuperCのメンバーとして見られるように
SuperC
~ c : int = 1
~ print( ) : void
SubC
~ c : int = 2
~ print( ) : void
~ print2( ) : void
int c = 1 void print( )
SubC 型インスタンス
e
int c = 2 void print2( )
オーバーライド されていない
オーバーライド されている
クラスSuperCから 継承した部分
図3.8 メソッドのオーバーライドとクラスのキャスト
なります。
クラスを継承すると、サブクラスの中にはスーパークラスから継承した部分とサブクラス独自の部分があります。こ
の場合、print2() メソッドはサブクラス独自の部分なので、図3.8 のような外側の四角内に位置に置かれます。一
方、print()メソッドは2 つのクラスの両方にあるのでオーバーライドされることになり、継承された部分(内側の
四角内)に置かれます。そのときの作り方は、まずクラスSuperCに入っているものをインスタンスeの中にコピーし てきて、その後で、クラスSubC の中身を書き込みます。よって、重複するもの(この場合、print()メソッド)は書 き換わります。
で、インスタンスe をスーパークラス SuperCにキャストしてみると、((SuperC) e) とは、インスタンスe の
クラスSuperC から継承された部分のみを見よ!ですから、メソッドprint2()は見えなくなってしまい、よってエ
ラー。これは、実数を整数にキャストする際に、小数点以下(整数に入れられない部分) が切り捨てられることと似た 雰囲気ですかね。
では、エラーの出た行をコメントアウトして、再び動かしてみて下さい。出力2 行目の1の出力については、後で 見ることにして、3 行目の出力がSubC.print()となることを解析してみて下さい。えっ?スーパークラスにキャス トしたのに、サブクラスのメソッドが動いていますね。はい、スーパークラスから継承した部分がオーバーライドされ ているので。インスタンスeのスーパークラス部分のprint()メソッドは、書き換えられているわけです。
さて、2行目の ((SuperC) e).cの出力 1についてはどうでしょう。メソッドのオーバーライドと同様に変数cも オーバーライドして値が2 かと思いきや、1 です!どうして?
結論から言うと、「フィールドはオーバーライドされない!」のです。
一般的に、クラスを継承する際に、スーパークラスで定義されたフィールドはサブクラスでそのまま利用しますか ら、「再宣言」などしません(型も名前も等しくて値が違うなんて、値だけ入れ直せば良いわけですよね)。従って、こ のプログラムのような書き方をすると、ちょっと困った行動をコンパイラはします。つまり、サブクラス内に、同じ 名前のフィールドを2 つ持ってしまうのです。で、単に e.cとすれば、オーバーライドされていない、本来のクラス SubC のcの値が、((SuperC) e).cとすれば、継承した部分に含まれているcの値が使われてしまうのです。
メソッドのオーバーライドは「あり」で、フィールドは「なし」はOKでしょうか。で、コンストラクタはどうで しょう。先にもちょっと触れましたね、「コンストラクタは継承されない」です。
コンストラクタの名前はそのクラスの名前と同じですから、サブクラスと名前の違うスーパークラスのコンストラ クタでは役に立たないわけで、継承もオーバーライドもできないわけです(ただし、サブクラスのコンストラクタ内で スーパークラスのコンストラクタを super()のように利用することはできますね)。とっても難しい!と思われるか
な。このところは十分に時間を掛けて理解しておきましょう。
スーパークラス型への代入
double x = 123;のような代入文では、右辺の整数型と左辺の実数型が違っても代入ができていますよね。これ
は継承関係にあるクラスの間ではどうでしょう。つまり、class A extends Bであるとき、A a = new B(); とか B b = new A();とか出来ないのでしょうか?
double x = 123;の場合、右辺の整数リテラルを実数値に変えて左辺に代入できるので可能なわけでした。ならば
同様に、右辺のコンストラクタで生成されたインスタンスが左辺の参照変数で参照可能か?を考えれば良いわけです。
まず、スーパークラスである (情報が少ない?) Bクラスのインスタンスにサブクラス型参照変数で参照 A a = new B();(図3.8中央 参照)して大丈夫でしょうか?そう、クラスA独自プラスαの部分が足りなそうですよね。つ まり、A a = new B(); はマズイということです。
一方、サブクラスである (情報が多い?) A クラスのインスタンスにはスーパークラスの継承部分が存在するので (図3.9右 参照)、スーパークラス型参照変数で参照B b = new A();しても情報が足りないということはありません。
つまり、B b = new A();はOKだということです。
ただし、B b = new A();としたとき、変数bからは、Aクラス独自の情報部分が見えていません。だって、bはあ くまで、自分の参照先はBクラスのインスタンスと思っているのですから。ちゃんと理解できるかな。
A B
A 型インスタンス B 型インスタンス a
A a = new B( );
A 型インスタンス B 型インスタンス b
B b = new A( );
この部分の情報が無い!
図3.9 スーパークラス型の参照変数
サブクラス型への強制代入
では、サブクラス型参照変数でスーパークラス型インスタンスを参照できない、と言いましたが、次の処理はどうで しょう?いま、class A extends Bとします(図3.9と同様)。
A a = new A(); // サブクラスのインスタンスを作成
B b = (B) a; // スーパークラス型参照変数に代入。b からはスーパークラスの情報のみ可視
A c = b; // 再度、サブクラスの参照変数で参照
3 行目はやはり無理な操作ですね。コンパイラとしてはスーパークラスの(たぶんサブクラスのインスタンスより情 報の少ない)インスタンスを情報の多いと思われるサブクラスの参照変数から参照させるのは無謀ということで、エラー が出されます。しかし、この場合bの周辺(?) にはクラスAの情報が囲んでいるのですから、プログラマの自己責任と いうことで、どうにかできないか?と思いますよね。これと同じようなことが、long l = 123L; int i = (int) l;
なんて処理で前期に出てきました。本来 long 型のデータはint型の変数に入れるのは無謀と思われますが、キャス トで無理やり押し込むことができる(このとき、値が壊れるか否かはプログラマの自己責任ですね)。
今回の場合も、3 行目をA c = (A) b; とキャストで書き換えると、変数c は変数a と同じ情報を持つことができ ます。
プログラマの自己責任とはいっても、なかなかミスは起こり得るので、そのクラスのインスタンスか否か(周りにサ ブクラスの情報がある?)を判定する演算子が欲しいところですね。それを実現するのが 「instanceof」演算子です。
これは、メソッドではなく、演算子 (五則演算などと一緒)です。「インスタンスinstanceofクラス」と使うことで、
そのクラスのインスタンスかどうかをbooleanの値で返してくれます。ただし、このとき、「サブクラスのインスタン スinstanceof スーパークラス」もtrueになるので、注意が必要です。
ソースコード3.44 InstanceOfExample.java (インスタンスの型チェック)