Java 基本・派生クラス間の参照の相互変換

クラス型変数は、それと同じクラス型の参照値を代入できます。

クラス型変数に、
クラス型変数と違うクラス型の参照値は代入できません。

参照の代入サンプル

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();が実行されます。

これは、派生クラスのメンバを呼び出す処理です。

以上の事から、
関数内では、引数によって、

基本・派生クラスのメンバ関数の呼び分けを行っている事が分かります。
このプログラム例は、一例です。
工夫次第では、まだまだ活用可能でしょう。