クラス型変数は、それと同じクラス型の参照値を代入できます。
クラス型変数に、
クラス型変数と違うクラス型の参照値は代入できません。
参照の代入サンプル
classA objA1 = new classA(); // OK
classA objA2 = new classA(); // OK
classB objB = new classB(); // OK
objA1 = objA2; // OK
objA1 = objB; // エラー
1つ目は、classA型の変数に、classAの参照が代入されていますので
問題はありません。
一方、2つ目は、classA型の変数に、classB型の参照が代入されていますので、
エラーとなり、実行できません。
これらは当然の事です。
しかし、基本クラスとその派生クラスでは、
共通する部分があるため、
基本クラス型変数 = 派生クラス型変数(派生クラスの参照値)
と言った代入が許されています。
これは、派生クラスの参照値が代入されると、
基本クラス型として扱われるのですが、
派生クラスはその中に基本クラスを含んでいるので、
全ての基本クラスメンバにアクセスできるためです。
代入後は、基本クラス型として働くので、
基本的には、派生クラスメンバへアクセスできません。
ちなみに、その逆の
派生クラス型変数 = 基本クラス型変数(基本クラスの参照値)
と言った代入は許されていません。
これは、基本クラスの参照値が代入されると、
派生クラス型として扱われるのですが、
参照値そのものは、基本クラスを指しているので、
派生クラスメンバにアクセスできないからです。
以上の説明で
基本クラス型変数 = 派生クラス型変数(派生クラスの参照値)
と言う代入ができる事が分かりました。
ここで1つ次のプログラムを見てみましょう。
基本クラス参照 = 派生クラス参照のサンプル
class A {
public int data1;
public void show_data1() { } // 関数内容は省略
}
class B extends A {
public int data2;
public void show_data2() { } // 関数内容は省略
}
public class Cmain {
public static void main(String[] args) {
A objA = new A();
B objB = new B();
objA = objB;
// objB = objA; // エラー
objA.data1 = 10; // OK
objA.show_data1(); // OK
// objA.data2 = 10; // エラー
// objA.show_data2(); // エラー
}
}
このプログラム例では、
基本クラス型変数 = 派生クラス型変数(派生クラスの参照値)
の代入が行われています。逆は無理です。
代入後は、基本クラス型として働くので、
基本的には、派生クラスメンバへアクセスできません。
と説明した通り、
派生クラスのdata2、show_data2にはアクセスできません。
次に、オーバーロードしたメンバや
オーバーライドしたメンバへのアクセスはどうなるか見てみましょう。
オーバーロードメンバについて
class A {
public int data;
public void show() { }
public void func() { }
public void func(int a) { } // 関数のオーバーロード
}
class B extends A {
public int data; // 変数のオーバーライド
public void show() { } // 関数のオーバーライド
public void func(int a, int b) { } // 関数のオーバーロード
}
public class Cmain {
public static void main(String[] args) {
A objA = new A();
B objB = new B();
objA = objB;
objA.data = 10; // 基本クラスのdataへアクセス
objA.show(); // 派生クラスのshowへアクセス
objA.func(); // 基本クラスのfunc()へアクセス
objA.func(5); // 基本クラスのfunc(int a)へアクセス
// objA.func(5, 10); // エラー
}
}
このプログラムで気になるところは、
派生クラスのオーバーライドした関数にアクセスできている事です。
基本クラス型変数 = 派生クラス型変数(派生クラスの参照値)
代入後は、基本クラス型として働くので、
基本的には、派生クラスメンバへアクセスできません。
と、説明しましたが、
オーバーライドした関数へはアクセス可能となります。
逆にオーバーライドされた基本クラスの関数へは、アクセス不可となります。
このように、
オーバーライドした関数と基本クラスのオーバーライドされた関数の
どちらを呼ぶか決定する処理を動的結合と言います。
次は、キャストを使って、
派生クラスメンバにアクセスする例を見てみましょう。
キャストを使った例
class A {
public int data1;
public void show_data1() { } // 関数内容は省略
}
class B extends A {
public int data2;
public void show_data2() { } // 関数内容は省略
}
public class Cmain {
public static void main(String[] args) {
A objA = new A();
B objB = new B();
objA = objB;
objA.data1 = 10; // OK
objA.show_data1(); // OK
// objA.data2 = 10; // エラー
// objA.show_data2(); // エラー
((B)objA).data2 = 10; // エラーにならない
((B)objA).show_data2(); // エラーにならない
}
}
この例では、
基本クラス型変数 = 派生クラス型変数(派生クラスの参照値)
の代入が行われた後、
キャストを使って派生クラスメンバにアクセスしている例です。
代入しても、objAには、
依然としてクラスBの参照値が格納されているので、
キャストすれば、
通常通り、全ての派生クラスメンバにアクセス可能となります。
関数の仮引数として基本クラス型の変数を指定する例
class A {
public void show() { } // 関数内容は省略
}
class B extends A {
public void show() { } // 関数内容は省略
}
public class Cmain {
public static void func(A obj) {
obj.show();
}
public static void main(String[] args) {
A objA = new A();
B objB = new B();
func(objA);
func(objB);
}
}
このプログラムは、
これまで説明してきた事を理解していれば、
何も疑問に思う事はないでしょう。
ただ、この例のように
基本クラス型変数を関数の仮引数にするメリットとしては、
基本クラス型の参照値も
その派生クラス型の参照値も受け取れる事です。
どちらも受け取れると言う事は、
その関数内で基本クラスと派生クラスに関する
同じような処理を行う事ができる。
等の利点があります。
どういう使い方をするかはプログラマ次第ですが、
プログラムの幅が広がる事は確かです。
関数の仮引数にするメリットを活用した例
class A {
public void show() { } // 関数内容は省略
}
class B extends A {
public void show() { } // 関数内容は省略
}
public class Cmain {
public static void func(A obj) {
if (obj instanceof B) {
((B)obj).show(); // 派生クラスのshow()呼び出し
} else {
obj.show(); // 基本クラスのshow()呼び出し
}
}
public static void main(String[] args) {
A objA = new A();
B objB = new B();
func(objA);
func(objB);
}
}
このプログラムでは、instanceof演算子を活用しています。
obj instanceof Bと言う記述は、
objにクラスBを含んでいるかどうかを判定します。
含んでいるならtrue、含まないならfalseを返します。
func(objA);の呼び出しがあった場合、
objAはクラスAの参照なので、
obj instanceof Bに対しては、クラスBを含まないので、falseが返され、
obj.show();が実行されます。
これは、基本クラスのメンバを呼び出す処理です。
一方、func(objB);の呼び出しがあった場合、
objBは、クラスBの参照なので、
obj instanceof Bに対しては、クラスBを含むので、trueが返され、
((B)obj).show();が実行されます。
これは、派生クラスのメンバを呼び出す処理です。
以上の事から、
関数内では、引数によって、
基本・派生クラスのメンバ関数の呼び分けを行っている事が分かります。
このプログラム例は、一例です。
工夫次第では、まだまだ活用可能でしょう。