Chapter 22

参照型の型変換

KeitoYuasa
KeitoYuasa
2023.02.25に更新

基本データ型の型変換と同様に、2つの変換方法がある。
①暗黙型変換
②キャストによる変換

①暗黙型変換

継承関係にあるクラス同士で、型変換が以下の場合、自動で型変換される。
つまり、子オブジェクトを親オブジェクトに代入する際、子オブジェクトは親のデータ型に自動変換される。

  • スーパークラスの型 = サブクラスの型
  • インターフェースの型 = 実装クラスの型

また、代入以外にもメソッドの引数や戻り値でも自動型変換が行われる。

Training.java
//スーパークラス
abstract class Super {
    int x; int y;
    public abstract void print();
    public void method(int x, int y){ this.x = x; this.y = y; }
}

//サブクラス
class Sub extends Super {
    public void print() {
        System.out.println("インスタンス変数:" + x + y);
    }
}

//実行クラス
class Training {
    public static void main(String[] args){
        Super obj = new Sub();
        obj.method(10, 20);
        obj.print(); //インスタンス化しているSubクラスのメソッドを実行する
    }
}

//出力結果:
//インスタンス変数:1020

②キャストによる変換

継承関係にあるクラス同士で、型変換が以下の場合、キャストしなければコンパイルエラーになる。
つまり、親オブジェクトを子オブジェクトに代入する際、親オブジェクトは子のデータ型にキャストしなければいけない。

  • サブクラスの型 = (サブクラスの型)スーパークラスの型
  • 実装クラスの型 = (実装クラスの型)インターフェースの型

また、代入以外にもメソッドの引数や戻り値でも自動型変換が行われる。

キャストが必要な理由

サブクラスや実装クラスで独自に定義したメソッドを実行するコードを書くとコンパイルエラーになるため。

これを理解するために、以下のクラスのメソッドを実行するまでの流れを理解する必要がある。

action メソッドを実行するまでの流れ
コンパイル時 コールしているクラス内に、呼び出そうとしているメソッドが定義されているか、外部クラスからprivate指定のメソッドを呼び出していないかをチェックする。(privateは外部クラスからアクセスできない。)
実行時 インスタンス化されているオブジェクトのメソッドが実行される。(インスタンスメソッドの場合。それ以外のメンバはスーバークラスのものが実行される。)

以下で、クラスのメソッドを実行するまでの流れの例を示す。

//スーパークラス
class Super { void methodA(){} }
//サブクラス
class Sub extends Super { 
    void methodA(){} 
    void methodB(){}
}

まず、コールしているSuperクラス内に、methodA, methodBがあるかをコンパイラが判定する。
以下では、Super.methodBがないのでコンパイルエラー。

class Test{
    Super obj = new Sub();
    obj.methodA();
    obj.methodB(); //コンパイルエラー
}

次のコードは、問題なく実行できる。
実行時は、インスタンス化されているSubクラスのメソッドが呼び出される。
また、Subクラスには、methodBが定義されている。インスタンスがSubクラスにキャストされているので、Subクラス側のメソッドが呼び出される。

class Test{
    Super obj1 = new Sub();
    obj1.methodA(); //SubクラスのmethodAを実行する
    
    Sub obj2 = (Sub)obj1;
    obj2.methodB();
}

privateメンバの継承

  • private指定のメンバは、サブクラスに継承されない。
  • private指定のメンバをサブクラスで上書きした場合、オーバーライドとみなされず新規のメソッドとみられる。
class A { private void foo(){System.out.println("TestA"); } }
class B extends A { private void foo(){System.out.println("TestB"); } }

以下ではコンパイルエラーになる。
まず、Aクラス内に、foo()メソッドがあるのかを判定される。これは問題ない。
しかし、A.foo()はprivate指定のメソッドであるため、外部のTestクラスからはアクセスできないためコンパイルエラーになる。

class Test {
    public static void main(String[] args){
        A obj = new B();
	obj.foo(); //コンパイルエラー
    }
}

instanceof

  • instaceof演算子:オブジェクトのデータ型を判定し、boolean型を返す。
  • また、具象クラス、抽象クラスの場合、継承関係のないオブジェクトとの比較は、コンパイルエラーになる。
Training.java
interface A {}
class B {}
class C extends B {}
class D {}
abstract class E {}

//実行クラス
class Training {
    public static void main(String[] args){
        C obj = new C();
        System.out.println(obj instanceof A);
        System.out.println(obj instanceof B);
        System.out.println(obj instanceof C);
        //System.out.println(obj instanceof D); コンパイルエラー
        //System.out.println(obj instanceof E); コンパイルエラー
    }
}
//出力結果:
//false
//true
//true

オーバーライド時に呼び出されるメンバ

子オブジェクトを親オブジェクトのデータ型に自動で変換した場合、インスタンスメソッド以外はスーパークラスのメンバが呼び出される。

Training.java
//スーパークラス
class Super {
    static String x = "スーパーstatic変数";
    String y = "スーパーインスタンス変数";
    static void methodA(){ System.out.println("スーパーstaticメソッド"); }
    void methodB(){ System.out.println("スーパーインスタンスメソッド"); }
}

//サブクラス
class Sub extends Super {
    static String x = "サブstatic変数";
    String y = "サブインスタンス変数";
    static void methodA(){ System.out.println("サブstaticメソッド"); }
    void methodB(){ System.out.println("サブインスタンスメソッド"); }
}
//実行クラス
class Training {
    public static void main(String[] args){
        Super obj = new Sub(); //自動型変換
        System.out.println(obj.x);
        System.out.println(obj.y);
        obj.methodA();
        obj.methodB();
    }
}
//出力結果:
//スーパーstatic変数
//スーパーインスタンス変数
//スーパーstaticメソッド
//サブインスタンスメソッド