第8章 継承

はじめに

継承は、1つのクラスを全て引き継いで新しいクラスを作成する方法です。既にあるクラスへ属性や機能を継ぎ足して新しいクラスを作成する方法です。すでにあるクラスに属性や機能を継ぎ足して新しいクラスを作る差分プログラミングを可能にします。この章では継承の仕組みを学習します。

目次

  • 継承
  • クラスの階層
  • protected修飾子
  • 参照型の自動型変換
  • ダウンキャスト
  • instanceof演算子
  • 引数における参照型の自動型変換
  • 戻り値における参照型の自動型変換
  • 章のまとめ

目標

継承を使ったクラスを作成できる様にすること。

継承

継承は、オブジェクトの設計図であるクラスを再利用するための技術です。
文字通り、既存のクラスを引き継いだ上で、さらに機能をつけて加えたり、あるいは一部の機能を変えたりして、新しいクラスを作ることができます。

例えば、既に上記のような社員情報を管理するEmployeeクラスが存在していたとします。その後、社員情報に加えて、営業情報も追加した新しいクラスを作成したいと依頼があった場合、一からクラスを新しく定義し直すと効率が悪いです。そんな時に継承を利用します。

例題(継承)

実際に継承を利用したクラスを作成してみましょう。

今回は、Employeeクラスというクラスを作成します。Employeeクラスは、属性として社員名前と社員IDの情報を保持し、操作として社員情報を表示することもできるクラスです。

項目 名前
プロジェクト alj_study
パッケージ chapter8
クラス名 Employee
package chapter8;

/**
* 社員情報クラス
* @version 1.0
* @author Yamamoto
*/
public class Employee {

/**
* 社員名を保持
*/
private String empName;

/**
* 社員番号を保持
*/
private int empId ;

/**
* コンストラクタ
*/
public Employee(String empName, int empId){
this.empName = empName;
this.empId = empId;
}

/**
* 社員データ情報表示
* @param 無し
* @return 無し
*/
public void disp(){
System.out.println("社員名:" + empName);
System.out.println("社員番号" + empId);
}
}

Employeeクラスを継承し、営業情報も追加したクラス(SalesEmpクラス)を作成します。

項目 名前
プロジェクト alj_study
パッケージ chapter8
クラス名 SalesEmp
継承 Employee
package chapter8;
/**
* 営業情報クラス
* @version 1.0
* @author Yamamoto
*/
public class SalesEmp extends Employee{

/**
* 営業部番号を保持
*/
private int salesId;

/**
* 個人売上高を保持
*/
private int salesVolume;

/**
* コンストラクタ
*/
public SalesEmp(String empName, int empId, int salesId, int salesVolume){
super(empName,empId); //スーパークラスのコンストラクタの呼び出し
this.salesId = salesId;
this.salesVolume = salesVolume;
}

/**
* 営業部情報表示
* @param 無し
* @return 無し
*/
public void empDisp(){
System.out.println("営業部番号:" + salesId);
System.out.println("個人売上高" + salesVolume);
}
}

実行するためのクラス(EmpExecクラス)を作成します。

項目 名前
プロジェクト alj_study
パッケージ chapter8
クラス名 EmpExec
package chapter8;

/**
* 実行する為のクラス
* @version 1.0
* @author Yamamoto
*/
public class EmpExec {

/**
* mainメソッド
*/
public static void main(String[] args) {

SalesEmp emp = new SalesEmp("福山雅治",1000,5000,120000000);
emp.empDisp();
}
}

コンパイル例:

> javac chapter8¥Employee.java
> javac chapter8¥SalesEmp.java
> javac chapter8¥EmpExec.java

実行例:

> java chapter8.EmpExec
営業部番号:5000
個人売上高120000000

継承の定義方法

クラスを継承するには、extendsキーワードを使用します。

extendsキーワードの構文は以下です。

SalesEmpクラスを宣言する際に付与されているextends Employeeが継承を意味します。今回はEmployeeクラス継承するという意味になります。

SalesEmpクラスはEmployeeクラスに定義されているメンバ変数やメソッドを引き継いでいます。

継承を利用した場合extendsキーワードで指定されているクラス(継承元のクラス)をスーパークラスといいます。
スーパークラスをを継承して作成する新しいクラスをサブクラスといいます。

上記の場合、SalesEmpクラスがスーパークラスEmployeeクラスがサブクラスです。

サブクラスのインスタンス化

Java言語の規約では、サブクラスのコンストラクタは、最初にスーパークラスのコンストラクタを実行しなければならないと決められています。

スーパークラスのコンストラクタを呼び出すためにはsuperキーワードを利用します。

[スーパクラスのコンストラスタをサブクラスから呼び出す場合]

super(引数リスト);

今回の場合、スーパークラスであるEmployeeクラスのコンストラクタが以下のように定義されています。

public Employee(String empName, int empId){
this.empName = empName;
this.empId = empId;
}

サブクラスのコンストラクタは、最初にスーパークラスのコンストラクタを実行しなければならないので、
スーパークラスのコンストラクタの定義に合わせて呼び出しを行わなければなりません
今回の場合、サブクラスであるSalesEmpクラスのコンストラクタ内で、最初にEmployeeクラスのコンストラクタを呼び出す必要があります。そのため、Employeeクラスではコンストラクタを以下のように定義します。

public SalesEmp(String empName, int empId, int salesId, int salesVolume){
super(empName,empId); //スーパークラスのコンストラクタの呼び出し
this.salesId = salesId;
this.salesVolume = salesVolume;
}

スーパークラスのコンストラクタが明示的に定義されている場合は、サブクラスのコンストラクタ内で必ず一番最初にスーパークラスのコンストラクタの呼び出しを定義しておかないと、コンパイルエラーになりますので注意して下さい。

スーパークラスのメンバ変数の呼び出し

サブクラス内スーパクラスのメンバ変数を利用するためには、スーパークラスに定義してあるメンバ変数名そのまま指定します。

先ほど作成しました、EmployeeクラスとSalesEmpクラスを以下のように修正してみましょう。

項目 名前
プロジェクト alj_study
パッケージ chapter8
クラス名 Employee
package chapter8;

/**
* 社員情報クラス
* @version 1.0
* @author Yamamoto
*/
public class Employee {

/**
* 社員名を保持
*/
private String empName;

/**
* 社員番号を保持
*/
private int empId;

//-----ここから(追加)-----
/**
* 入社日を保持
*/
String hireDate = "2019/10/01";

/**
* 誕生日を保持
*/
String birthDate = "2000/01/01";
//-----ここまで(追加)-----

/**
* コンストラクタ
*/
public Employee(String empName, int empId){
this.empName = empName;
this.empId = empId;
}

/**
* 社員データ情報表示
* @param 無し
* @return 無し
*/
public void disp(){
System.out.println("社員名:" + empName);
System.out.println("社員番号" + empId);
}
}
項目 名前
プロジェクト alj_study
パッケージ chapter8
クラス名 SalesEmp
継承 Employee
package chapter8;
/**
* 営業情報クラス
* @version 1.0
* @author Yamamoto
*/
public class SalesEmp extends Employee{

/**
* 営業部番号を保持
*/
private int salesId;

/**
* 個人売上高を保持
*/
private int salesVolume;

/**
* コンストラクタ
*/
public SalesEmp(String empName, int empId, int salesId, int salesVolume){
super(empName,empId); //スーパークラスのコンストラクタの呼び出し
this.salesId = salesId;
this.salesVolume = salesVolume;
}

/**
* 情報表示
* @param 無し
* @return 無し
*/
public void empDisp(){
//-----ここから(修正)-----
System.out.println("入社日:" + hireDate); //入社日を表示
System.out.println("誕生日:" + super.birthDate); //誕生日を表示
//-----ここまで(修正)-----
}
}

コンパイル例:

> javac chapter8¥Employee.java
> javac chapter8¥SalesEmp.java

実行例:

> java chapter8.EmpExec
入社日:2019/10/01
誕生日:2000/01/01

また、スーパークラスで定義したメンバ変数と同じ名前の変数サブクラスのメンバ変数として定義することも可能です。
そうした場合、サブクラス内スーパークラスのメンバ変数を呼び出したい場合は以下のように定義します。

[スーパークラスで定義しているメンバ変数を、サブクラス内で利用したい場合]

super.スーパークラスで定義したメンバ変数名

SalesEmpクラスを以下のように修正してみましょう。

項目 名前
プロジェクト alj_study
パッケージ chapter8
クラス名 SalesEmp
継承 Employee
package chapter8;
/**
* 営業情報クラス
* @version 1.0
* @author Yamamoto
*/
public class SalesEmp extends Employee{

//-----ここから(追加)-----
/**
* 誕生日を保持
*/
String birthDate = "1980/01/01";
//-----ここまで(追加)-----

/**
* 営業部番号を保持
*/
private int salesId;

/**
* 個人売上高を保持
*/
private int salesVolume;

/**
* コンストラクタ
*/
public SalesEmp(String empName, int empId, int salesId, int salesVolume){
super(empName,empId); //スーパークラスのコンストラクタの呼び出し
this.salesId = salesId;
this.salesVolume = salesVolume;
}

/**
* 社員データおよび営業部情報表示
* @param 無し
* @return 無し
*/
public void empDisp(){
//-----ここから(修正)-----
System.out.println("誕生日(スーパークラス):" + super.birthDate); //SalesEmpクラスのメンバ変数(birthDate)の呼び出し
System.out.println("誕生日(サブクラス):" + birthDate); //SalesEmpクラスのメンバ変数(birthDate)の呼び出し
//-----ここまで(修正)-----
}
}

コンパイル例:

> javac chapter8¥Employee.java
> javac chapter8¥SalesEmp.java

実行例:

> java chapter8.EmpExec
誕生日(スーパークラス):2000/01/01
誕生日(サブクラス):1980/01/01

サブクラス内で定義しているメンバ変数と、スーパークラスで定義しているメンバ変数同じ名前の場合は、superキーワードを指定すると、スーパークラスで定義しているメンバ変数を利用することができます。

スーパークラスのメンバメソッドの呼びだし

サブクラス内スーパークラスメンバメソッドを呼び出したい場合、スーパクラスのメソッド名を指定します。

SalesEmpクラスの以下の箇所を修正し、実行してみましょう。

項目 名前
プロジェクト alj_study
パッケージ chapter8
クラス名 SalesEmp
継承 Employee
package chapter8;
/**
* 営業情報クラス
* @version 1.0
* @author Yamamoto
*/
public class SalesEmp extends Employee{

/**
* 誕生日を保持
*/
String birthDate = "1980/01/01";

/**
* 営業部番号を保持
*/
private int salesId;

/**
* 個人売上高を保持
*/
private int salesVolume;

/**
* コンストラクタ
*/
public SalesEmp(String empName, int empId, int salesId, int salesVolume){
super(empName,empId); //スーパークラスのコンストラクタの呼び出し
this.salesId = salesId;
this.salesVolume = salesVolume;
}

/**
* 社員データおよび営業部情報表示
* @param 無し
* @return 無し
*/
public void empDisp(){

//-----ここから(修正)-----
disp(); //スーパークラスのdisp()メソッドの呼び出し
//-----ここまで(修正)-----

}
}

コンパイル例:

> javac chapter8¥SalesEmp.java

実行例:

> java chapter8.EmpExec
社員名:福山雅治
社員番号1000

継承とis-a関係

is-a関係とは、「A is a B」という関係を表す表現技法のことです。
ある2つのオブジェクトを、Aはサブクラス、Bはスーパークラスとして当てはめた時に「A is a B」という表現が成りたつと、AとBは継承していると証明することができます。

先ほど作成した、EmployeeクラスとSalesEmpクラスをis-a関係で表現すると以下になります。

SalesEmpクラスはEmployeeクラスの一種です。

上記、is-a関係SalesEmpクラスとEmployeeクラスは成立する為、継承関係であることが証明できます。

継承されないもの

サブクラスへ継承されるものは、オブジェクトです。オブジェクトの中に存在しない以下の2つは、継承の対象ではありません。

  • コンストラクタ
  • 静的メンバ(static修飾子が付加されたメンバ)

例題(static修飾子が付加されたメンバ)

static修飾子が付加されたメンバは、以下のようにアクセスも可能ですので注意して下さい。

項目 名前
プロジェクト alj_study
パッケージ chapter8
クラス名 AAAA
package chapter8;
/**
* AAAクラス
* @version 1.0
* @author Yamamoto
*/
public class AAAA {
static int n ;
}
項目 名前
プロジェクト alj_study
パッケージ chapter8
クラス名 BBBB
継承 AAAA
package chapter8;
/**
* AAAクラス
* @version 1.0
* @author Yamamoto
*/
public class BBBB extends AAAA {

}
項目 名前
プロジェクト alj_study
パッケージ chapter8
クラス名 Exec
package chapter8;
/**
* 営業情報クラス
* @version 1.0
* @author Yamamoto
*/
public class Exec{

public static void main(String[] args){
AAAA a = new AAAA();
BBBB b = new BBBB();
a.n = 10; //コンパイルエラーにならない
b.n = 20; //コンパイルエラーにならない
System.out.println(AAA.a); //クラス名.変数名

}

}

コンパイル例:

> javac chapter8¥AAAA.java
> javac chapter8¥BBBB.java
> javac chapter8¥Exec.java

実行例:

> java chapter8.Exec
20

アクセスが制限されたメンバ

static修飾子が付加された変数はクラス変数で、インスタンス化しなくても「クラス名.変数名」で利用ができます。
インスタンス化し、参照型の変数を通してもアクセスもできますが、本来は「クラス名.変数名」でアクセスすることが一般的です。

また、次の修飾子によって、アクセスが制限されたメンバも継承できません。

  • final修飾子が付加されたクラス
  • private修飾子されたメンバ

    final修飾子は「変更ができない」ことを表す修飾子です。クラスに付与すると、継承できないクラスになります。クラス以外にもメソッドメンバ変数final修飾子を付与することができます。final修飾子が付与されたメンバ変数は、初期設定した内容を変更できなくなります。final修飾子が付与されたメソッドは、オーバーライド(後述で説明)出来なくなります。

private修飾子が付与されているメンバ(メンバ変数メソッド)は、クラス内のみアクセス可能な修飾子です。

例題(アクセスが制限されたメンバ)

先ほど作成した

項目 名前
プロジェクト alj_study
パッケージ chapter8
クラス名 AAAA
package chapter8;
/**
* AAAクラス
* @version 1.0
* @author Yamamoto
*/
final public class AAAA {
static int n ;
}
項目 名前
プロジェクト alj_study
パッケージ chapter8
クラス名 BBBB
継承 AAAA
package chapter8;
/**
* AAAクラス
* @version 1.0
* @author Yamamoto
*/
public class BBBB extends AAAA {

}

コンパイル例:

> javac chapter8¥AAAA.java
> javac chapter8¥BBBB.java
chapter8/BBBB.java:7: エラー: final AAAAからは継承できません
public class BBBB extends AAAA {
^
エラー1個

クラスの階層

継承関係図示したものを、継承ツリーといいます。Java言語では、Objectクラスを起源として階層構造で継承しています。
すべてのクラスはObjectクラスのサブクラスです。
クラス宣言何も継承されていない場合は、コンパイラが「extends Object」を自動的に挿入します。

単一継承

1つのクラスを継承することを単一継承といいます。逆に2つ以上のクラスを同時に継承することを多重継承といいます。
Java言語単一継承です。多重継承を行いたい場合は、インターフェースを利用します。

Objectクラスのメソッド

Objectクラスには、いくつかの基本的なメソッドが定義されています。これらは全てのオブジェクトに継承されるメソッドです。

Objectクラスのメソッド

メソッド名 機能
toString() オブジェクトの文字列表現を返す
clone() このオブジェクトのコピーを作成して返す
equals(Object obj) このオブジェクトと他のオブジェクトが等しいかどうか示す
hashCode() オブジェクトのハッシュコードを返す
finalize() ガベージコレクション用
getClass() このオブジェクトの実行時クラスを返す
notify() マルチスレッド処理用。待機中のスレッドを一つ再開する。
notifyAll() マルチスレッド処理用。待機中のスレッドを全て再開する。
wait() マルチスレッド処理用。スレッドを待機させる。
wait(timeout) マルチスレッド処理用。制限時間付きでスレッドを待機させる。
wait(timeout , nanos) マルチスレッド処理用。制限時間付きでスレッドを待機させる。

Objectクラスは全てのクラスに必要の為、明示的に継承しなくてもコンパイル時に自動的に継承される仕組みになっています。つまり全てのクラスは、暗黙のうちにObjectクラスを継承したクラスなのです。

継承によるデフォルトコンストラクタ

サブクラスのコンストラクタでは、最初にスーパークラスのコンストラクタを実行して、スーパークラスのオブジェクトを初期化する必要がありました。

サブクラスのコンストラクタ内に、スーパクラスのコンストラクタの呼び出し明示的に定義されていない場合、スーパークラスのコンストラクタを呼び出すためのデフォルトコンストラクタ(super();)が、コンパイラによって自動的に挿入されます。

例題(継承によるデフォルトコンストラクタ)

それでは、以下のConstructorChainクラスを作成し、実行結果を確認してみてください。

項目 名前 備考
プロジェクト alj_study
パッケージ chapter8
クラス名 Data
クラス名 DataA Dataクラスを継承
クラス名 DataB DataAクラスを継承
クラス名 ConstructorChainExec
ファイル名 ConstructorChainExec.java
package chapter8;

/**
* Dataクラス
* @version 1.0
* @author Yamamoto
*/
class Data{

/**
* Dataのコンストラクタ
*/
public Data(){
System.out.println("Data");
}
}

/**
* DataAクラス
*/
class DataA extends Data{

/**
* DataAクラスのコンストラクタ
*/
public DataA(){
System.out.println("DataA");
}
}

/**
* DataBクラス
*/
class DataB extends DataA{

/**
* DataBクラスのコンストラクタ
*/
public DataB(){
System.out.println("DataB");
}
}

/**
* 実行する為のクラス
*/
public class ConstructorChainExec{

/**
* メインメソッド
*/
public static void main(String[] args){
DataB b = new DataB();
}

}

コンパイル例:

> javac chapter8¥ConstructorChainExec.java

実行例:

> java chapter8.ConstructorChainExec
Data
DataA
DataB

;

上記の場合、サブクラスには、スーパークラスのコンストラクタの呼び出しが明示的にされていない為、コンパイルする際に暗黙の内にsuper()が挿入されます。

その為、Testクラスで「new DataB()」を実行すると、まずDataAクラスのコンストラクタが起動されます。

/**
* Bクラスのコンストラクタ
*/
public DataB(){
super(); //コンパイラによって暗黙の内に挿入される(DataAクラスのコンストラクタの呼び出し)
System.out.println("DataB");
}

しかしながら、DataAクラスはDataクラスを継承しているので、Dataクラスのコンストラクタが起動します。

/**
* DataAクラス
*/
class DataA extends Data{

/**
* DataAクラスのコンストラクタ
*/
public DataA(){
super(); //コンパイラによって暗黙の内に挿入される(Dataクラスのコンストラクタの呼び出し)
System.out.println("DataA");
}
}

Dataクラスではextendsキーワードが指定されていないので、明示的には何も継承されていませんが、明示的に継承されていない場合には、暗黙的にObjectクラスのコンストラクタが継承されます。その為、最初に実行されるコンストラクタはObjectクラスのコンストラクタが実行されます。

/**
* Dataのコンストラクタ
*/   
public Data(){
    super();        //コンパイラによって暗黙の内に挿入される(Objectクラスのコンストラクタの呼び出し) 
    System.out.println("Data");
}

その為、Dataクラスのコンストラクタ、DataAクラスのコンストラクタ、DataBクラスのコンストラクタが実行され、以下のようにコンソールへ出力されます。

Data
DataA
DataB

protected修飾子

今までpublic、private、デフォルトアクセスについて解説してきましたが、もう一つアクセスするための修飾子があります。それはprotected修飾子です。protected修飾子は、継承と関わりがある修飾子です。

protected修飾子の役割

protected修飾子の役割は以下の2つです。

  • サブクラスからアクセスできるようにする(別のパッケージからでも、継承していればアクセスすることができる)
  • 同じパッケージのクラスからアクセスできるようにする(デフォルト修飾子と同じ役割)

protected修飾子はデフォルトアクセス修飾子に、サブクラスの範囲追加した修飾子です。

protected修飾子は、サブクラスを設計しやすくするために設けられた修飾子で、デフォルトアクセス修飾子よりもカプセル化さらに弱める効果があります。

例題(別パッケージのクラスを継承している場合)

項目 名前
プロジェクト alj_study
パッケージ chapter8A
クラス名 C
package chapter8A;

/**
* Cクラス
* @version 1.0
* @author Yamamoto
*/
public class C {

/**
* 値を保持
*/
protected double value;

/**
* 表示機能
* @param 無し
* @return 無し
*/
protected void message(){
System.out.println("Cクラスです。");
}
}
項目 名前
プロジェクト alj_study
パッケージ chapter8B
クラス名 D
継承 chapter8A.C
package chapter8B;

import chapter8A.C;

/**
* Dクラス
* @version 1.0
* @author Yamamoto
*/
public class D extends C{

/**
* スーパークラス情報表示機能
* @param 無し
* @return 無し
*/
public void display(){
value = 1.0; //protectedのメンバ変数へアクセスできる
System.out.println(value);
message(); //protectedのメソッドへアクセスできる
}

}
項目 名前
プロジェクト alj_study
パッケージ chapter8B
クラス名 ExecCD
package chapter8B;

/**
* 実行するためのクラス(ExecCDクラス)
* @version 1.0
* @author Yamamoto
*/
public class ExecCD {

/**
* メインメソッド
*/
public static void main(String[] args) {
D d = new D();
d.display();
}

}

コンパイル例:

> javac chapter8A¥C.java
> javac chapter8B¥D.java
> javac chapter8B¥ExecCD.java

実行例:

> java chapter8B.ExecCD
1.0
Cクラスです。

protected修飾子が付加されているメンバは、別のパッケージからでも、継承していればアクセスすることができます。

例題(同じパッケージのクラスを継承している場合)

新たに以下のクラスを作成し、確認してみましょう。

項目 名前
プロジェクト alj_study
パッケージ chapter8
クラス名 A
package chapter8;

/**
* Aクラス
* @version 1.0
* @author Yamamoto
*/
public class A {

/**
* データを保持
*/
protected double value;

/**
* メッセージ表示
*/
protected void message(){
System.out.println("Aクラスです。");
}

}
項目 名前 備考
プロジェクト alj_study
パッケージ chapter8
クラス名 B
継承 chapter8.A
package chapter8;

/**
* Bクラス
* @version 1.0
* @author Yamamoto
*/
public class B extends A{

public void display(){
value = 1.0; //protectedのメンバ変数へアクセスできる
System.out.println(value);
message(); //protectedのメソッドへアクセスできる
}

}
項目 名前
プロジェクト alj_study
パッケージ chapter8
クラス名 ExecAB
package chapter8;

/**
* 実行するためのクラス(ABクラス)
* @version 1.0
* @author Yamamoto
*/
public class ExecAB{

/**
* メインメソッド
*/
public static void main(String[] args) {
B b = new B();
b.display();
}

}

コンパイル例:

> javac chapter8¥A.java
> javac chapter8¥B.java
> javac chapter8¥ExecAB.java

実行例:

> java chapter8.ExecAB
1.0
Aクラスです

protected修飾子を付加したメンバは、同じパッケージからもアクセスできます。
(デフォルト修飾子と同じ役割を果たします。)

アクセス修飾子のまとめ

修飾子 意味
public パッケージを問わず、どのクラスからでも直接アクセスが可能
protected 同一パッケージの外部クラス、またはしたサブクラスからであればアクセスが可能
修飾子なし 同一パッケージの外部クラスからであればアクセス可能
private 外部クラスからのアクセスはできず、同一クラス内のメソッドからのみアクセス可能

アクセス修飾子の適用場所

アクセス修飾子はクラス、コンストラクタ、クラスメンバ(メンバ変数、メソッド)、インターフェースにつけることができます。
(ただし、classにはprivateとprotectedは適用できません。またインターフェースはpublic以外は適用できません。)

アクセス修飾子 class コンストラクタ メンバ変数 メソッド インターフェース
public
protected × ×
修飾子なし ×
private × ×

参照型の自動型変換

基本データ型では自動型変換キャストすることで、型は違っていても代入することができる仕組みがありました。オブジェクトを扱う参照型も同様な仕組みがあります。
ここでは参照型自動型変換について学習します。

参照型の自動型変換の条件

参照型自動型変換をおこなう場合、次のような条件があります。

  • 同じ継承ツリーの中に限る
  • 継承ツリーの矢印の向きにのみ可能

例題(参照型の自動型変換)

以下、Company←Employee←SalesEmpという同じ継承ツリーにあるクラスで、参照をスーパークラス(親クラス)の変数に代入して使用できるかどうかを確認している例題です。

以下のクラスを作成し、実行結果を確認してみましょう。

項目 名前 備考
プロジェクト alj_study
パッケージ chapter8
クラス名 Company
package chapter8;
/**
* 会社情報クラス
* @version 1.0
* @author Yamamoto
*/
public class Company{

void comDisp(){
System.out.println("Company class");
}
}
項目 名前 備考
プロジェクト alj_study
パッケージ chapter8
クラス名 Employee
継承 Company
package chapter8;
/**
* 社員情報クラス
* @version 1.0
* @author Yamamoto
*/
public class Employee extends Company{

void empDisp(){
System.out.println("Employee class");
}
}
項目 名前 備考
プロジェクト alj_study
パッケージ chapter8
クラス名 SalesEmp
継承 Employee
package chapter8;
/**
* 営業情報クラス
* @version 1.0
* @author Yamamoto
*/
public class SalesEmp extends Employee{

void salesDisp(){
System.out.println("SalesEmp class");
}
}
項目 名前 備考
プロジェクト alj_study
パッケージ chapter8
クラス名 Exec
package chapter8;

/**
* 実行するためのクラス(Execクラス)
* @version 1.0
* @author Yamamoto
*/
public class Exec {

/**
* メインメソッド
*/
public static void main(String[] args) {

SalesEmp s = new SalesEmp(); //SalesEmpクラスからオブジェクトを作成し、SalesEmp型の変数sへ代入
Employee e = s; //SalesEmp型の変数sをEmployeeクラス型の変数eへ代入
Company c = s; //SalesEmp型の変数sをCompanyクラス型の変数cへ代入

System.out.println("-----sの変数範囲でアクセス------")
s.salesDisp();
s.empDisp();
s.comDisp();

System.out.println();
System.out.println("-----eの変数範囲でアクセス------")
e.empDisp();
e.comDisp();

System.out.println();
System.out.println("-----cの変数範囲でアクセス------")
c.comDisp();
}

}

コンパイル例:

> javac chapter¥SalesEmp.java
> javac chapter8¥Employee.java
> javac chapter8¥SalesEmp.java
> javac chapter8¥Exec.java

実行例:

> java chapter8.Exec
-----sの変数範囲でアクセス------
SalesEmp class
Employee class
Company class

-----eの変数範囲でアクセス------
Employee class
Company class

-----cの変数範囲でアクセス------
Company class

上記、SalesEmpクラスのオブジェクトを一つだけ作成し、その参照をeとcといった、スーパクラスの変数へ代入しています。
eとcはそれぞれのクラスの該当部分を参照し、empDisp()メソッドやcomDisp()メソッドを実行できていることがわかります。

尚、Execクラスへ以下を追記すると、コンパイルエラーになります。

e.salesDisp();
c.salesDisp();
c.empDisp();

参照範囲外メンバアクセスはできない仕組みになってます。

ダウンキャスト

通常、継承ツリーの矢印と逆向き代入しようとすると、コンパイルエラーが発生します。

コンパイルエラーを発生させずに代入するには、ダウンキャストをおこないます。

ダウンキャストの定義方法

ダウンキャストするためには、括弧を使用して明示的に型を指定します。

サブクラス型 変数名 = (サブクラス型)スーパークラス型の参照

例題(ダウンキャスト)

先ほど作成したExecクラスを以下のように修正してみましょう。

項目 名前 備考
プロジェクト alj_study
パッケージ chapter8
クラス名 Exec
package chapter8;

/**
* 実行するためのクラス(Execクラス)
* @version 1.0
* @author Yamamoto
*/
public class Exec {

/**
* メインメソッド
*/
public static void main(String[] args) {


SalesEmp s = new SalesEmp(); //SalesEmpクラスからオブジェクトを作成し、SalesEmp型の変数sへ代入
Employee e = s; //SalesEmp型の変数sをEmployeeクラス型の変数eへ代入

//-----ここから(修正)-----
SalesEmp s1 = e; //Employee型の変数eを、SalesEmp型の変数s1へ代入するとコンパイルエラーが発生する

System.out.println("-----s1の変数範囲でアクセス------");
s1.salesDisp();
s1.empDisp();
s1.comDisp();
//-----ここまで(修正)-----

}

}

コンパイル例:

> javac chapter8¥Exec.java
chapter8/Exec.java:20: エラー: 不適合な型: EmployeeをSalesEmpに変換できません:
SalesEmp s1 = e; //Employee型の変数eを、SalesEmp型の変数s1へ代入するとコンパイルエラーが発生する
^
エラー1個

上記、スーパークラス(Employee)をサブクラス(SalesEmp)へ代入しようとしていて、コンパイルエラーが発生しています。

Execクラスを以下のように修正してください。

項目 名前 備考
プロジェクト alj_study
パッケージ chapter8
クラス名 Exec
package chapter8;

/**
* 実行するためのクラス(Execクラス)
* @version 1.0
* @author Yamamoto
*/
public class Exec {

/**
* メインメソッド
*/
public static void main(String[] args) {


SalesEmp s = new SalesEmp(); //SalesEmpクラスからオブジェクトを作成し、SalesEmp型の変数sへ代入
Employee e = s; //SalesEmp型の変数sをEmployeeクラス型の変数eへ代入

//-----ここから(修正)-----
SalesEmp s1 = (SalesEmp)e; //ダウンキャスト
//-----ここまで(修正)-----

System.out.println("-----s1の変数範囲でアクセス------");
s1.salesDisp();
s1.empDisp();
s1.comDisp();


}

}

コンパイル例:

> javac chapter8¥Exec.java

実行例:

> java chapter8.Exec
-----s1の変数範囲でアクセス------
SalesEmp class
Employee class
Company class

ダウンキャストをおこなうと、コンパイルエラーが発生せず実行する事ができます。

元々メモリ上に、SalesEmpオブジェクトが生成されているので、ダウンキャストしても参照範囲が一致する為、問題なく実行することができます。

ダウンキャストしても、メモリ上に作成されているオブジェクトがスーパークラスの場合は、実行時に例外が発生します。

Execクラスを以下のように修正してください。

項目 名前 備考
プロジェクト alj_study
パッケージ chapter8
クラス名 Exec
package chapter8;

/**
* 実行するためのクラス(Execクラス)
* @version 1.0
* @author Yamamoto
*/
public class Exec {

/**
* メインメソッド
*/
public static void main(String[] args) {

//-----ここから(修正)-----
Employee e = new Employee(); //Employeeオブジェクトを生成し、Employeeクラス型の変数eへ代入
//-----ここまで(修正)-----

SalesEmp s1 = (SalesEmp)e; //ダウンキャスト

System.out.println("-----s1の変数範囲でアクセス------");
s1.salesDisp();
s1.empDisp();
s1.comDisp();

}

}

コンパイル例:

> javac chapter8¥Exec.java

実行例:

> java chapter8.Exec
Exception in thread "main" java.lang.ClassCastException: class chapter8.Employee cannot be cast to class chapter8.SalesEmp (chapter8.Employee and chapter8.SalesEmp are in unnamed module of loader 'app')
at chapter8.Exec.main(Exec.java:19)

メモリー上に作成されているオブジェクトはスーパークラスであるEmployeeオブジェクトです。しかしながら
s1の変数にはサブクラス型の参照が代入されています。オブジェクトのメンバを超えて参照することができる為、
実行時例外(java.lang.ClassCastException)が発生します。

※ダウンキャストを行う際は、注意して定義してください。

instanceof演算子

参照型の変数が、どんな型のオブジェクトか確認したい場合、instanceof演算子を利用します。

instanceof演算子は以下のように利用します。

変数 instanceof  型

変数参照しているオブジェクトの型が、同じかあるいはそのサブクラスならtrueを返します。

例題(instanceof演算子)

以下クラスを作成し、実行結果を確認してみましょう。

項目 名前 備考
プロジェクト alj_study
パッケージ chapter8
クラス名 Food
package chapter8;
/**
* Foodクラス
* @version 1.0
* @author Yamamoto
*/
public class Food{

void foodDisp(){
System.out.println("Food class");
}
}
項目 名前 備考
プロジェクト alj_study
パッケージ chapter8
クラス名 Fruit
継承 Food
package chapter8;
/**
* Fruitクラス
* @version 1.0
* @author Yamamoto
*/
public class Fruit extends Food{

void fruitDisp(){
System.out.println("Fruit class");
}
}
項目 名前 備考
プロジェクト alj_study
パッケージ chapter8
クラス名 Apple
継承 Fruit
package chapter8;
/**
* Appleクラス
* @version 1.0
* @author Yamamoto
*/
public class Apple extends Fruit{

void appleDisp(){
System.out.println("Apple class");
}
}
項目 名前 備考
プロジェクト alj_study
パッケージ chapter8
クラス名 AppleObjExec
package chapter8;

/**
* 実行する為のクラス
* @version 1.0
* @author Yamamoto
*/
public class AppleObjExec {

/**
* mainメソッド
*/
public static void main(String[] args) {

Food f = new Apple();

//検査してからダウンキャストをおこなう
if(f instanceof Apple){

System.out.println("ダウンキャスト実行します");
Apple a = (Apple)f; //ダウンキャスト

}

}
}

コンパイル例:

> javac chapter8¥Food.java
> javac chapter8¥Fruit.java
> javac chapter8¥Apple.java
> javac chapter8¥AppleObjExec.java

実行例:

> java chapter8.AppleObjExec
ダウンキャスト実行します

is-a関係とinstanceofの実行結果

is-a関係とは、継承を表す表現技法です。

X is a Z
  • XはZ型である
  • XはZを継承している
  • XはZの派生クラスである
  • XはZのサブクラスである

instanceof演算子の結果がtrueの場合は、is-a関係が成立します。

先ほど作成しましたAppleObjExecクラスを修正し、実行結果を確認してみましょう。

項目 名前 備考
プロジェクト alj_study
パッケージ chapter8
クラス名 AppleObjExec
package chapter8;

/**
* 実行する為のクラス
* @version 1.0
* @author Yamamoto
*/
public class AppleObjExec {

/**
* mainメソッド
*/
public static void main(String[] args) {

//-----ここから(修正)-----
Apple apple = new Apple();
System.out.println("-----Appleオブジェクト-----");
System.out.println( apple instanceof Food); //true
System.out.println( apple instanceof Fruit); //true
System.out.println( apple instanceof Apple); //true

Fruit fruit = new Fruit();
System.out.println("-----Fruitオブジェクト-----");
System.out.println( fruit instanceof Food); //true
System.out.println( fruit instanceof Fruit); //true
System.out.println( fruit instanceof Apple); //false

Food food = new Food();
System.out.println("-----Foodオブジェクト-----");
System.out.println( food instanceof Food); //true
System.out.println( food instanceof Fruit); //true
System.out.println( food instanceof Apple); //false
//-----ここまで(修正)-----
}
}

コンパイル例:

> javac chapter8¥Food.java
> javac chapter8¥Fruit.java
> javac chapter8¥Apple.java
> javac chapter8¥AppleObjExec.java

実行例:

> java chapter8.AppleObjExec
-----Appleクラス-----
true
true
true
-----Fruitクラス-----
true
true
false
-----Foodクラス-----
true
false
false

上記の場合、変数appleはFoodクラスのサブクラスのサブクラスであり、Fruitクラスのサブクラス、Appleクラスである為、
全てtrueを返します。
変数fruitはFoodクラスのサブクラスであり、Fruitクラスである為、trueを返します。Appleクラスとの関係はスーパークラスにあたる為、falseを返します。
変数foodはFoodクラスの為、trueを返します。

引数における参照型の自動型変換

メソッドの引数(仮引数)が参照型の場合、メソッドを実行するときの引数(実引数)へ、サブクラスの引数指定することができます。

例題(引数における参照型の自動型変換)

新たに、CookクラスとCookObjExecクラスを作成し、実行結果を確認してください。

項目 名前 備考
プロジェクト alj_study
パッケージ chapter8
クラス名 Cook
package chapter8;
/**
* Cookクラス
* @version 1.0
* @author Yamamoto
*/
public class Cook{
public void bake(Food f){
f.foodDisp();
}
}
項目 名前 備考
プロジェクト alj_study
パッケージ chapter8
クラス名 CookObjExec
package chapter8;
/**
* 実行するためのクラス
* @version 1.0
* @author Yamamoto
*/
public class CookObjExec{
public static void main(String[] args){
Apple apple = new Apple();
Fruit fruit = new Fruit();
Cook cook = new Cook();

System.out.println("---Appleオブジェクトを実引数に指定---");
cook.bake(apple);

System.out.println("---Fruitオブジェクトを実引数に指定---");
cook.bake(fruit);

}
}

コンパイル例:

> javac chapter8¥Cook.java
> javac chapter8¥CookObjExec.java

実行例:

> java chapter8.CookObjExec
---Appleオブジェクトを実引数に指定---
Food class
---Fruitオブジェクトを実引数に指定---
Food class

Cookクラスのbakeメソッドは引数(仮引数)にFood型の参照が定義されています。
「cook.bake(apple);」では、Food型のサブクラスであるApple型の変数appleを引数(実引数)へ指定しています。
実行すると、サブクラスであるappleが自動型変換されて、スーパークラス型のfoodへ代入されます。
そしてFoodクラスのメソッドfoodDisp()メソッドが実行され、「Food class」とコンソール上に表示されます。

※「cook.bake(fruit);」も同様に自動変換され、「Food class」とコンソール上に表示されます。

戻り値における参照型の自動型変換

メソッドの戻り値が参照型の場合、戻り値型に指定されている型のサブクラスを返すことができます。

例題(戻り値における参照型の自動型変換)

新たに以下を作成し、確認してください。

項目 名前 備考
プロジェクト alj_study
パッケージ chapter8
クラス名 FoodCheck
package chapter8;
/**
* FoodCheckクラス
* @version 1.0
* @author Yamamoto
*/
public class FoodCheck{
public Food get(int foodNo){
if(foodNo == 1){
return new Apple();
}else{
return new Fruit();
}
}
}
項目 名前 備考
プロジェクト alj_study
パッケージ chapter8
クラス名 FoodCheckExec
package chapter8;
/**
* 実行するためのクラス
* @version 1.0
* @author Yamamoto
*/
public class FoodCheckExec{
public static void main(String[] args){
FoodCheck fch = new FoodCheck();
Food food = fch.get(1);
food.foodDisp();
}
}

FoodCheckクラスのget()メソッドは引数にint型の1を指定するとFood型のサブクラスのAppleオブジェクトを返します。
1以外を指定すると、Food型のサブクラスであるFruitオブジェクトを返します。上記の場合、「fch.get(1);」を指定しており、実行するとAppleオブジェクトが返されます。

まとめ

以下の要点をしっかり理解してから次の章へ進んでください。

継承

  • 継承は、オブジェクトの設計図であるクラス再利用するための技術である
  • 継承は、既存のクラス引き継いだ上で、さらに機能をつけて加えたり、あるいは一部の機能を変えたりして、新しいクラスを作ることができる
  • クラスを継承するには、extendsキーワードを使用する
  • extendsキーワードのに、継承したいクラス名を指定する
  • extendsキーワードの後に指定したクラス(継承元)スーパークラス(親クラス)という
  • 継承によって新たに作成するクラス(継承先)サブクラス(子クラス)という
  • サブクラス(子クラス)の定義には、スーパクラスに追加したい定義追記する
  • extendsキーワードの後に指定できるクラスは1つのみである(単一継承
  • サブクラス(子クラス)は、スーパークラス(親クラス)のメンバ(フィールド)やメソッドを自分のものとして扱えることができる
  • superキーワードを利用すると、スーパークラスメンバ(変数やメソッド)コンストラクタ呼び出すことができる
  • サブクラスをインスタンス化すると、スーパークラス、サブクラスの順に生成される
  • サブクラスのコンストラクタが呼ばれると、スーパクラスのコンストラクタが呼ばれる
  • サブクラスのコンストラクタは、最初にスーパークラスのコンストラクタを実行しなければならない
  • スーパークラスのコンストラクタの定義に合わせて呼び出しを行わなければならない
  • スーパークラスのコンストラクタを呼び出す場合は「super(引数リスト);」と記載する
  • サブクラス内でスーパークラスのメンバ変数を利用したい場合、スーパークラスのメンバ変数名がそのまま利用できる。
  • スーパークラスのメンバ変数と同じ名前のメンバ変数をサブクラス内に定義することもできる
  • 呼び出す場合には、superキーワードを利用する
  • サブクラス内でスーパークラスのメンバメソッドを利用したい場合、スーパークラスのメンバメソッド名で利用できる。
  • is-a関係とは、「A is a B」という関係を表す表現技法のこと
  • Aがサブクラス、Bがスーパークラスとして当てはめた時、「A is a B」の文章が成り立つと継承関係であることが証明できる
  • オブジェクト内に存在しないコンストラクタや静的メンバ(static修飾子)は継承されない
  • final修飾子やprivate修飾子が付加されたメンバは継承されない

クラスの階層

  • 継承関係を図示したものを、継承ツリーといい、Objectクラスを起源として継承している
  • すべてのクラスはObjectクラスのサブクラスである
  • クラス宣言に何も継承されていない場合は、コンパイラが「extends Object」を自動的に挿入する
  • 1つのクラスを継承することを単一継承という
  • 2つ以上のクラスを同時に継承することを多重継承という
  • Javaは単一継承である
  • 多重継承を行いたい場合は、インターフェースを利用する
  • オブジェクトクラスには、いくつかの基本的なメソッドが定義されている
  • Objectクラスで定義されているメソッドは、全てのオブジェクトへ継承されるメソッドである
  • サブクラスのコンストラクタにスーパークラスのコンストラクタの呼び出しが定義されていない場合、コンパイラは、スーパークラスを呼び出すためのデフォルトコンストラクタ(super();)を暗黙の内に挿入する
  • デフォルトコンストラクタのアクセス修飾子はクラスのアクセス修飾子と同じになる
  • 継承では、サクブラスにおけるコンストラクタの呼び出しが次々とスーパークラスに波及する
  • extendsを明示的に記述していないクラスは、Objectクラスを継承している

protected修飾子

  • protected修飾子は、サブクラスを設計しやすくするために設けられた修飾子
  • 同じパッケージのクラスからアクセスできるようにする(デフォルト修飾子と同じ役割)
  • サブクラスからアクセスできるようにする(別のパッケージからでも、継承していればアクセスすることができる)
アクセス修飾子 class コンストラクタ メンバ変数 メソッド インターフェース
public
protected × ×
修飾子なし ×
private × ×

参照型の自動型変換

  • 基本データ型と同じように参照型でも自動型変換される仕組みがある
  • 参照型の自動型変換対象は、同じ継承ツリー内に限られる
  • サブクラスのオブジェクトの参照は、スーパクラスの変数へ代入できる(自動型変換可能)
  • 参照型の変数が、オブジェクトへアクセスできる範囲(参照範囲)は、変数の型によって決まる
  • 自動型変換により代入されると、スーパクラスで定義されているメンバ名の範囲で、サブクラスオブジェクトへアクセス(参照)することができる
  • 自動型変換により代入されると、スーパクラスで定義されているメンバ名の範囲外はアクセスすることができない(アクセス(参照)しようとするとコンパイルエラーが発生する)

ダウンキャスト

  • 継承ツリーの矢印と逆向きへ代入しようとすると、コンパイルエラーが発生する
  • 継承ツリーの矢印と逆向きへプログラマが明示的代入することをダウンキャストという
  • ダウンキャストは、同じ継承ツリー内であれば、どんなクラスも変換可能
  • オブジェクトが元々サブクラスのオブジェクトであれば問題ないが、そうでない場合は実行時例外発生する可能性がある
  • ダウンキャストするとコンパイルエラーならない
  • 元からサブクラス型のオブジェクトを参照していると、実行時例外は発生しない
  • 元からサブクラス型のオブジェクトを参照していない実行時例外(java.lang.ClassCastException)が発生し、停止する

instanceof演算子

  • instanceof演算子は、変数が参照しているオブジェクトの型調べる演算子である
  • 変数が参照しているオブジェクトの型が、同じかあるいはそのサブクラスならtrueを返す
  • is-a関係とは、継承を表す表現技法である
  • instanceof演算子の結果がtrueの場合、is-a関係が成立する

引数における参照型の自動型変換

  • メソッドの引数(仮引数)参照型の場合、メソッドを実行するときの引数(実引数)へサブクラス引数を指定することができる

戻り値における参照型の自動型変換

  • メソッドの戻り値参照型の場合、戻り値型に指定されている型のサブクラス返すことができる