サブクラスからスーパークラスのメンバを扱う

1.3 サブクラスからスーパークラスのメンバを扱う

 サブクラスはスーパークラスのメンバを継承して、オブジェクト生成から利用することは確認できました。
 オブジェクトを生成して利用するだけではなく、直接サブクラス内からスーパークラスのメンバを利用することも可能です。ではその利用方法について学習していきます。

1.3.1 スーパークラスを表すsuperキーワードについて

 クラスを拡張して作成したサブクラス内ではsuper(スーパー)というキーワードが利用できます。これは2章で学習したオブジェクト自身を指す「this」キーワードの関係に似ています。thisキーワードがサブクラスのオブジェクト自身を意味するように、superキーワードはスーパークラスのオブジェクトのことを意味します。
 このsuperキーワードを利用すればサブクラス内からも、スーパークラスのメンバにアクセスすることが可能になります。
 以下にsuperキーワードを利用した基本構文を示します。

 基本構文でスーパークラスのメンバにアクセスする場合、「super」をつけてアクセスすると説明しましたが、実はつけなくてもアクセスすることは可能です。この仕組みもthisと同じです。但しサブクラスのローカル変数やメンバの名前に同じ物がある場合、必ずsuperをつけないといけません。これは名前の優先度が以下のようになっているためです。
 サブクラスのローカル変数名 > サブクラスのメンバ名 > スーパークラスのメンバ名

 この優先度を無効にしてメンバにアクセスしたい場合は、「this」または「super」を明示的につけましょう。

1.3.2 サブクラスのオブジェクト作成時のコンストラクタ呼び出しルール

 サブクラスのオブジェクト生成を行うと、スーパークラスのコンストラクタが自動で呼び出されることは前節で少し触れました。前節で利用したNotePc1クラスを見直してみましょう。スーパークラスのコンストラクタを呼び出している記述が無いことが確認できます。記述が無い場合は引数なしのコンストラクタが呼び出されるルールがあります。図1.3.1にはその処理の流れを示します。

➢ NotePc1.java ※サブクラス

図 1.3.1: 指定なしのコンストラクタ呼び出しの流れ

 サブクラスとスーパークラスのコンストラクタの機能には、以下のような仕組みがあることを紹介します。
 ※先程説明した仕組みも含めています。下記の※1に関しては前節のプログラムで確認できていますので、残り項目に関しては後の項で改めて紹介します。

  • サブクラスのオブジェクト生成を行うと、スーパークラスのコンストラクタが必ず呼び出される。 ※1
  • スーパークラスのコンストラクタ呼び出しはサブクラス内では省略できる。 ※1
  • スーパークラスのコンストラクタ呼び出しをサブクラス内で省略したら、引数なしコンストラクタが自動で呼び出される。※1
  • サブクラスでスーパークラスのコンストラクタ呼び出しを省略した時に、スーパークラスに引数ありのコンストラクタがあり、且つ、引数なしのコンストラクタの記述がない場合、コンパイルエラーとなる。
  • スーパークラスにコンストラクタの記述が全く無い場合は、デフォルトコンストラクタが呼び出されるのでコンパイルエラーにならない。
  • スーパークラスのコンストラクタ呼び出しを明示的に行う場合は、サブクラスのコンストラクタ処理の先頭に記述する必要がある。
  • 別のコンストラクタを呼び出す時は先頭に記述する制約があり、コンストラクタ内から呼び出せるのは1つのコンストラクタに限定される為、スーパークラスのコンストラクタ呼び出しを明示的に行うと、サブクラス内の別のコンストラクタを呼び出すことはできない。
  • ポイント
    • サブクラスのオブジェクト生成を行うと、スーパークラスのコンストラクタが必ず呼び出される。
    • サブクラスの記述にスーパークラスのコンストラクタ呼び出し記述は省略できるが、省略すると引数なしのコンストラクタが自動で呼び出される。

     先ずはスーパークラスのコンストラクタを、明示的に呼び出すプログラムを紹介します。

    1.3.3 サブクラスからスーパークラスのコンストラクタを明示的に呼ぶプログラム

     サブクラスからスーパークラスのコンストラクタを、super()を用いて明示的に呼び出して動作することを確認します。superキーワードの利用方法について学習しましょう。

    ソースコード

    ➢ Computer1.java ※スーパークラス

     1.2.1で作成したクラスを利用します。

    ① ソース・フォルダー      :myproj_basic/src
    ② パッケージ          :jp.co.f1.basic.ch11
    ③ 名前             :NotePc2

    ➢ NotePc2.java ※サブクラス
    package jp.co.f1.basic.ch11;
    
    //Computerクラスを継承
    public class NotePc2 extends Computer1 {
    	private String useType; // 用途タイプ
    
    	// コンストラクタ(引数なし)
    	public NotePc2() {
    		this.useType = null;
     		System.out.println("ノートパソコンを作成しました。");
     	}
     	// コンストラクタ(引数あり)
     	public NotePc2(String os, int memory, String useType) {
     		// スーパークラスのコンストラクタを呼び出す
     		super(os, memory);
     		this.useType = useType;
     		System.out.println("タイプは" + this.useType + "用のノートパソコンを作成しました。");
     	}
     	public void setUseType(String useType) {
     		this.useType = useType;
     		System.out.println("タイプは" + this.useType + "用にしました。");
     	}
     }
    

    クラス説明
     ※このNotePc2クラスはNotePc1クラスの処理内容に、引数ありのコンストラクタを追加しているだけです。その違いの部分について説明を行います。
     13~18行目が引数ありのコンストラクタを定義しています。このコンストラクタは引数として文字列でOS名、数値でメモリ数、文字列で用途を受け取ります。内部処理として15行目でスーパークラスの引数ありのコンストラクタを呼び出します。この記述はブロックの必ず初めに書かなければいけないルールとなっています。※コメントは処理記述ではないので対象外
     16行目の処理でフィールド変数のuseTypeに引数で受け取った値を設定し、17行目でメッセージ出力を行います。
       13:public NotePc2(String os , int memory , String useType){
       14: //スーパークラスのコンストラクタを呼び出す
       15: super(os,memory);
       16: this.useType = useType;
       17: System.out.println("タイプは" + this.useType + "用のノートパソコンを作成しました。");
       18:}

    ① ソース・フォルダー      :myproj_basic/src
    ② パッケージ          :jp.co.f1.basic.ch11
    ③ 名前             :UseSubClass2
    ④ 作成するメソッド・スタブの選択:public static void main(String[] args) にチェックを入れる

    ➢ UseSubClass2.java ※mainメソッドを持つ実行クラス
    package jp.co.f1.basic.ch11;
    
    public class UseSubClass2 {
    	public static void main(String[] args) {
    		//サブクラスのオブジェクトを生成
    		NotePc2 npc = new NotePc2("Windows7",3072,"ビジネス");
    	}
    }
    

    実行結果

    解説

     UseSubClass2クラスの6行目でNotePc2クラスの、オブジェクト生成を引数ありのコンストラクタで行っています。引数にはOS名、メモリ数、用途の3つの情報を渡しています。
       6:NotePc2 npc = new NotePc2("Windows7",3072,"ビジネス");

     mainメソッドで行う処理は6行目しかありませんが、ここから順にサブクラスのコンストラクタ→スーパークラスのコンストラクタ呼び出しと続いていきます。各処理が終わると順次呼び出し元へと、処理が返ってくるのはこれまでの仕組みと同じです。
     呼び出される流れは前項と変わりませんが、今回の呼び出しの流れを図1.3.2で示します。

    図 1.3.2: 明示的なコンストラクタ呼び出しの流れ

    ポイント
    • サブクラスのオブジェクトを生成する際に、スーパークラスのコンストラクタを明示的に呼び出す場合、super()を利用し呼び出すことができる。但し処理記述は必ずサブクラス内コンストラクタの先頭に記述する必要がある。

     次の項でサブクラスとスーパークラスのコンストラクタの仕組みについて確認していきます。

    1.3.4 サブクラスとスーパークラスのコンストラクタの仕組みについての確認

     1.3.2項で示したサブクラスとスーパークラスのコンストラクタの仕組みについて、実際に示した通りになるのかソースコード画面から確認します。

     
  • サブクラスでスーパークラスのコンストラクタ呼び出しを省略した時に、スーパークラスに引数ありコンストラクタがある場合、引数なしコンストラクタを必ず記述しないとコンパイルエラーになるケースについて確認する。
  •  
  • スーパークラスにコンストラクタの記述が全く無い場合は、デフォルトコンストラクタが呼び出されるのでコンパイルエラーにならないケースについて確認する。
  •  
  • スーパークラスのコンストラクタ呼び出しを明示的に行う場合は、サブクラスのコンストラクタ処理の先頭に必ず記述する必要があるケースについて確認する。
  •  
  • スーパークラスのコンストラクタ呼び出しを明示的に行うと、サブクラス内の別のコンストラクタを呼び出すことはできないケースについて確認する。
    ※別のコンストラクタを呼び出す時は先頭に記述する制約があり、コンストラクタ内から呼び出せるのは1つのコンストラクタに限定される為です。
  •  これまでの画面の結果で紹介してきた4つケースも、1.3.2項で示した通りになることが確認できました。サブクラスとスーパークラスの間には、様々な仕組みがあることを忘れないようにして下さい。

    1.3.5 サブクラスからスーパークラスのメンバを利用する

     superキーワードを利用して(※省略も可能)サブクラスからスーパークラスのメンバヘアクセスできることは説明しました。しかし無条件でアクセスできるとは限らない場合もあります。3章で学習したアクセス修飾子の仕組みを思い出して下さい。このアクセス修飾子がサブクラスとスーパークラスの間にも密接な関係があることを説明していきます。
     まずはこれまでのプログラムで利用してきたComputer1.javaのソースコードを見てください。

    ➢ Computer1.java

     これまでスーパークラスとして利用してきたComputer1クラスです。この2つのフィールド変数のアクセス修飾子は「private」になっていることが確認できます。
     アクセス修飾子がprivateメンバにはクラスの外からはアクセスできない仕組みになっています。クラスを拡張しメンバを引き継げてもこのアクセス修飾子の仕組みは当然サブクラスに適応され、スーパークラスのprivateメンバには、サブクラスからもアクセスすることはできないようになっています。
     では実際にソースコードからアクセスできないことを確認していきます。

    1.3.6 サブクラスからスーパークラスのprivateメンバを利用するプログラム

     サブクラスからスーパークラスのprivateメンバへ、アクセスできないことを確認します。

    ソースコード

    ➢ Computer1.java ※スーパークラス

     1.2.1で作成したクラスを利用します。

    ① ソース・フォルダー      :myproj_basic/src
    ② パッケージ          :jp.co.f1.basic.ch11
    ③ 名前             :NotePc3

    ➢ NotePc3.java ※サブクラス
    package jp.co.f1.basic.ch11;
    
    //Computerクラスを継承
    public class NotePc3 extends Computer1 {
    	private String useType; //用途タイプ
    
    	//コンストラクタ(引数なし)
    	public NotePc3() {
    		this.useType = null;
    		System.out.println("ノートパソコンを作成しました。");
    	}
    	//コンストラクタ(引数あり)
    	public NotePc3(String os , int memory , String useType){
    		//スーパークラスのコンストラクタを呼び出す
    		super(os,memory);
    		this.useType = useType;
    		System.out.println("タイプは" + this.useType + "用のノートパソコンを作成しました。");
    	}
    	public void setUseType(String useType){
    		this.useType = useType;
    		System.out.println("タイプは" + this.useType + "用にしました。");
    	}
    	public void showInfo(){
    		System.out.println("このノートパソコンのOSは" + super.os + "です。");
    		System.out.println("メモリサイズは" + super.memory + "です。");
    		System.out.println("タイプは" + this.useType + "用です。");
    	}
    }
    

    Eclipse上でのソースファイル結果

    解説

     このNotePc3クラスはNotePc2クラスの処理内容に、スーパークラスにアクセスするshowInfoメソッドを追加しているだけです。その追加部分について説明します。
     23~27行目のshowInfo()メソッドの内部処理で、スーパークラスのprivateフィールド変数のosとmemoryにアクセスしています。
       23:public void showInfo(){
       24: System.out.println("このノートパソコンのOSは" + super.os + "です。");
       25: System.out.println("メモリサイズは" + super.memory + "です。");
       26: System.out.println("タイプは" + this.useType + "用です。");
       27:}

     Eclipse上でのソースファイル結果を見てもらうと分かるように、エラーが表示されています。詳細を見ると「フィールドComputer1.osは不可視です」(※画面にはありませんが、memoryの詳細も同様になります)と表示されています。これはスーパークラスのフィールド変数がprivateになっている為、外部から見えない(アクセス不可)と言うことを表しています。

    ポイント
    • サブクラスからもスーパークラスのprivateメンバには直接アクセスはできない。

     スーパークラスのメンバにアクセスできない制限があると不便な場合もあります。ここでアクセス修飾子についてさらに思い出してください。privateが外部クラスからのアクセスを遮断するのに対して、外部アクセスを完全に許可する「public」、サブクラスからのアクセスなら許可する「protected」があります。
     このアクセス修飾子を上手に利用すればこの不便な制限を解消することができます。
     例えば以下の3つの方法があります。
     1)全てのメンバのアクセス修飾子をpublicに変更する。
     2)サブクラスからアクセス可能な修飾子protectedを利用する。
     3)privateメンバにアクセスできるpublicまたはprotectedメソッドをスーパークラスに用意する。

     1)の手順は最も簡単な手順ですが、Javaのカプセル化の概念を無視してしまうのであまりお勧めできません。
     2)と3)の手順をうまく組み合わせればカプセル化の概念も守り、スーパークラスとサブクラスの間で適切なコードを記述しやすくなります。

    クラスの3つの機能

     この章で学習した「継承」、3章で学習した「カプセル化」、5章で少し出てきた「多態性」という3つの機能は、Javaのクラスがもつ大変便利な機能です。この3つの機能を使いこなすことで、誤りの少ないプログラムを効率的に作成できるようになります。この3つの機能はJavaには欠かせないものなのです。

     では次の項よりアクセス修飾子protectedをもつスーパークラスのメンバへアクセスするサンプルを紹介します。

    1.3.7 サブクラスからスーパークラスのprotectedメンバを利用するプログラム

     サブクラスからスーパークラスの、アクセス修飾子protectedのメンバにアクセスできることを確認します。

    ソースコード

    ① ソース・フォルダー      :myproj_basic/src
    ② パッケージ          :jp.co.f1.basic.ch11
    ③ 名前             :Computer2

    ➢ Computer2.java ※スーパークラス
    package jp.co.f1.basic.ch11;
    
    public class Computer2 {
    	protected String  os;
    	protected int memory;
    
    	//コンストラクタ(引数なし)
    	public Computer2(){
    		this.os = null;
    		this.memory = 0;
    		System.out.println("パソコンを作成しました。");
    	}
    	//コンストラクタ(引数あり)
    	public Computer2(String os, int memory){
    		this.os = os;
    		this.memory = memory;
    		System.out.println("OS「" + os + "」メモリサイズ「" + memory + "MByte」のパソコンを作成しました。");
    	}
    	public void setOsMemory(String os,int memory){
    		this.os = os;
    		this.memory = memory;
    		System.out.println("OSを「" + os + "」にメモリを「" + memory + "MByte」に変更しました。");
    	}
    	public void show(){
    		System.out.println("パソコンのOSは「" + os + "」です。");
    		System.out.println("メモリサイズは「" + memory + "MByte」です。");
    	}
    }	
    

    解説

     Computer1クラスの内容からフィールド変数のアクセス修飾子を、privateからprotectedに変更したクラスです。※変更は4、5行目のみです。

    ① ソース・フォルダー      :myproj_basic/src
    ② パッケージ          :jp.co.f1.basic.ch11
    ③ 名前             :NotePc4

    ➢ NotePc4.java ※サブクラス
    package jp.co.f1.basic.ch11;
    
    //Computerクラスを継承
    public class NotePc4 extends Computer2 {
    	private String useType; //用途タイプ
    
    	//コンストラクタ(引数なし)
    	public NotePc4() {
    		this.useType = null;
    		System.out.println("ノートパソコンを作成しました。");
    	}
    	//コンストラクタ(引数あり)
    	public NotePc4(String os , int memory , String useType){
    		//スーパークラスのコンストラクタを呼び出す
    		super(os,memory);
    		this.useType = useType;
    		System.out.println("タイプは" + this.useType + "用のノートパソコンを作成しました。");
    	}
    	public void setUseType(String useType){
    		this.useType = useType;
    		System.out.println("タイプは" + this.useType + "用にしました。");
    	}
    	public void showInfo(){
    		System.out.println("このノートパソコンのOSは" + super.os + "です。");
    		System.out.println("メモリサイズは" + super.memory + "です。");
    		System.out.println("タイプは" + this.useType + "用です。");
    	}
    }
    

    解説

     NotePc3クラスの内容から拡張元をComputer1クラスからComputer2クラスに変更したクラスです。※変更は4行目のみです。
       4:public class NotePc4 extends Computer2 {

     23~27行目のshowInfo()メソッドの内部処理で、スーパークラスのprotectedフィールド変数のosとmemoryにアクセスしています。protectedはサブクラスからのアクセスを許可するため、今回はコンパイルエラーにはなりません。
       23:public void showInfo(){
       24: System.out.println("このノートパソコンのOSは" + super.os + "です。");
       25: System.out.println("メモリサイズは" + super.memory + "です。");
       26: System.out.println("タイプは" + this.useType + "用です。");
       27:}

    ① ソース・フォルダー      :myproj_basic/src
    ② パッケージ          :jp.co.f1.basic.ch11
    ③ 名前             :UseProtectedMember
    ④ 作成するメソッド・スタブの選択:public static void main(String[] args) にチェックを入れる

    ➢ UseProtectedMember.java ※mainメソッドを持つ実行クラス
    package jp.co.f1.basic.ch11;
    	
    public class UseProtectedMember {
    	public static void main(String[] args) {
    		//サブクラスのオブジェクトを生成
    		NotePc4 npc = new NotePc4("Windows7",3072,"ビジネス");
    		npc.showInfo();
    	}
    }
    

    実行結果

    解説

     UseProtectedMemberクラスの6行目でNotePc4クラスの、オブジェクト生成を引数ありのコンストラクタで行っています。引数にはOS名、メモリ数、用途の3つの情報を渡しています。
       4:public class NotePc4 extends Computer2 {

     23~27行目のshowInfo()メソッドの内部処理で、スーパークラスのprotectedフィールド変数のosとmemoryにアクセスしています。protectedはサブクラスからのアクセスを許可するため、今回はコンパイルエラーにはなりません。
       6:NotePc4 npc = new NotePc4("Windows7",3072,"ビジネス");

     7行目でインスタンスメソッドであるshowInfo()メソッドを呼び出して、このノートパソコンの情報を表示しています。
       7:npc.showInfo();

     実行結果からも分かるように、UseProtectedMemberクラスからサブクラスのshowInfo()メソッドを利用して、スーパークラスのアクセス修飾子protectedフィールド変数の値を表示しています。

     今回のプログラムではサブクラスのメソッド内からprotectedのフィールド変数だけにアクセスしていますが、protectedのメソッドやpublicのメンバ(フィールド変数、メソッド)にも当然アクセスすることが可能です。
     繰り返し説明になってしまいますが、拡張したクラスはスーパークラスのメンバを引き継げるため、サブクラス内では継承した機能と追加した機能をうまく組み合わせて使うことで、効率良くプログラミングが行えるようになります。この仕組みは忘れずにきちんと覚えておきましょう。

    ポイント
    • サブクラスからスーパークラスのprotectedメンバにはアクセスが可能である。

    NEXT>> 1.4 オーバーライドについて