blog

JavaSE 第10回 オブジェクト指向の継承

この記事では、引き続きオブジェクト指向プログラミングを学ぶために、オブジェクト指向のカプセル化を研究されている、標準クラスを定義し、オブジェクトを作成して使用する方法を知っています。オブジェクト指向に...

Nov 13, 2020 · 16 min. read
シェア

この記事では、オブジェクト指向のカプセル化を勉強した後、標準的なクラスを定義する方法とオブジェクトを作成して使用する方法を知って、オブジェクト指向プログラミングの勉強を続けていきます。オブジェクト指向の特徴として、カプセル化、継承、ポリモーフィズムの3つがあります。次に、オブジェクト指向における継承について学びます。

第1章 相続

- 相続の概要

相続とは何ですか?

物事の相互関係を指す「継承」。

人生において "相続 "という言葉は、どちらかというと "息子の父への継承"、つまり息子と父の間に "相続 "という関係があると理解されています。

Javaプログラミングでは、クラスは物事を記述するために使用されます。そのため、オブジェクト指向プログラミングでは、「継承」はクラス間の関係、通常はサブクラスとその親の関係を指し、サブクラスは親からプライベートでないメンバを継承することができます。

なぜ相続が必要なのですか?

相続のメリットは何ですか?

人生において、父親の財産を相続した息子が数十年、あるいは一生を棒に振ることは明らかです。

プログラミングにおける継承の役割とは?まず、ある要件を見てみましょう:

  • 犬、猫、牛など、いくつかの動物をオブジェクト指向の方法で説明しなさい。
    • 犬:名前、年齢、性別、毛の色、食べ物が好き、ドアに注意できる
    • 猫は、名前、年齢、性別、毛の色、食べ物が大好き、甘えることができます。
    • 牛は、名前、年齢、性別、毛の色、食べ物が大好き、土地を耕します。

現時点でこの要件に直面した場合、通常、それを説明するために3つのカテゴリーが定義され、以下のように説明されます:

観察していると、猫も犬も牛も、別々のクラスで定義されているメンバーには共通点があるという現象があることがわかります。

問題点:このようにプログラミングで発生するコードの冗長性は、将来、より多くの動物種が登場したり、共通のメンバーを再定義しなければならなくなったりした場合、プロジェクト全体から見れば、コードはますます冗長になり、ますます肥大化し、後の段階でプログラムを維持することは容易ではありません。では、どうすれば解決できるのでしょうか?

解決策:この時点で、継承を使って共通点を抽出し、それを再利用させることで、コードの冗長性の問題を解決することができます。この方法は、上位のクラスを抽象化し、親クラスで共通性を定義し、その共通性を持つ他のクラスをサブクラスとして親クラスから継承させます。

上記の要件では、親クラスである動物クラスを抽象化することができます。これらはすべて動物であり、動物は属性-名前、年齢、毛色、性別を持ち、すべて食べます:

つまり、オブジェクト指向プログラミングにおける継承の役割です:

  • コードの再利用性を高め、コードの冗長性を削減します。
  • 継承はポリモーフィズムの基礎

概要

  1. 継承とは
    • 継承とは、サブクラスと親クラスの関係のことで、サブクラスは親クラスのメンバを継承することができます。
  2. 継承の役割
    • コードの再利用性を高め、コードの冗長性を削減します。

- 継承の形式

以上の説明を通して、継承とは何か、継承の役割を知り、次にJavaで継承を定義する形式を学びます。

フォーマッティング

キーワード:エクステンド

class   {
	...
}

class サブクラスは親クラスを継承する {
	...
}

代表例

親クラス:動物

package www.penglei666.com.demo03;
/**
 * 動物クラス、親クラス
 */
public class Animal {
 String name;
 int age;
 String color;
 String gender;
 public void eat(){
 System.out.println("僕は食いしん坊なんだ!");
 }
}

サブカテゴリ: 犬

package www.penglei666.com.demo03;

/**
 * Dogクラス、サブクラス、Animalを継承する
 */
public class Dog extends Animal {
 public void alert(){
 System.out.println("私は清掃員だ。");
 }
}

テストクラス:テスト

package www.penglei666.com.demo03;

public class Test {
 public static void main(String[] args) {
 Dog wc = new Dog();
 // 親クラスの属性が使える:名前、年齢、性別、色。
 wc.name = " ";
 wc.age = 10;
 wc.gender = "公";
 wc.color = "yellow";
 // 親クラスのメソッドを呼び出すこともできるし、独自のメソッドを呼び出すこともできる。
 wc.eat(); // 僕は食いしん坊なんだ!
 wc.alert(); // 私は清掃員だ。

 }
}

上のコードでは、DogクラスはextendsキーワードでAnimalクラスを継承しているので、DogクラスはAnimalクラスのサブクラスです。

実行結果を見れば一目瞭然ですが、名前、年齢、性別などの属性が定義されていないにもかかわらず、サブクラスはその属性を操作したり、メソッドを食べたりすることができます。これは、サブクラスが親クラスを継承すると、自動的に親クラスのメンバを持つことを意味します。

-スーパーキーワード

親クラスのメンバと子クラスのメンバの名前を変更した場合、子クラスのオブジェクトが名前を変更したメンバを呼び出すとどうなりますか?

サブクラスと親クラスのメンバの名前が変更された場合

親:動物

package www.penglei666.com.demo03;
/**
 * 動物クラス、親クラス
 */
public class Animal {
 int age = 10;
}

サブクラス: Dog

package www.penglei666.com.demo03;

/**
 * Dogクラス、サブクラス、Animalを継承する
 */
public class Dog extends Animal {
 int age = 11;
 public void printAge(){
 System.out.println(" " + this.age);
 }
}

テストクラス:テスト

package www.penglei666.com.demo03;

public class Test {
 public static void main(String[] args) {
 Dog wc = new Dog();
 wc.printAge(); // 出力:年齢:11
 }
}

出力でわかるように、出力は親クラスの結果値ではなく、サブクラスそのものです。

では、名前を変更した場合、親クラスのメンバにはどのようにアクセスするのでしょうか?この場合、キーワードsuper を使用します。

this""キーワードは、呼び出し元自身への参照を表し、superキーワードは親クラスへの参照を表します。

スーパーキーワードの使用

使用書式:super.parent クラス・メンバ変数名

サブクラス・コードの変更:Dogクラス

package www.penglei666.com.demo03;

/**
 * Dogクラス、サブクラス、Animalを継承する
 */
public class Dog extends Animal {
 int age = 11;
 public void printAge(){
 System.out.println("父親である:" + super.age);
 System.out.println("サブステージだ:" + this.age);
 }
}

テストクラス:テスト

package www.penglei666.com.demo03;

public class Test {
 public static void main(String[] args) {
 Dog wc = new Dog();
 wc.printAge(); 
 /* 出力結果
 親の年齢:10
子供の年齢:11歳
 */
 }
}

-スーパーと "this "キーワード

親クラス空間は、子クラスオブジェクトの生成よりも優先されます。

サブクラス・オブジェクトが作成されるたびに、そのサブクラス・オブジェクト自体が作成される前に、親クラス空間が初期化されます。

その目的は、対応する親クラスのスペースを含むサブクラスオブジェクトは、親クラスのメンバを含むことができ、親クラスのメンバが私的に変更されない場合、サブクラスは親クラスのメンバを自由に使用できることです。

このコードは、サブクラスのコンストラクタ・メソッドが呼び出される際には、まず親クラスのコンストラクタ・メソッドが呼び出されなければならないことに反映されています。図解を理解すると次のようになります:

スーパーと "これ "の意味:

  • super: 親クラスの記憶領域を指定します
  • ""this"":現在のオブジェクトへの参照を表します。

super と "this" を使ったメンバ・プロパティとメソッドの呼び出し。

this.メンバ変数 	--  
super.メンバ変数 	--  

this.メンバー・メソッド name() 	--   
super.メンバー・メソッド name() --  

superと "this "を使ったコンストラクタの呼び出し。

this(...) 	-- このクラスのコンストラクタ
super(...) 	-- 親クラスのコンストラクタ・メソッド

サブクラスは、各コンストラクタ・メソッドでデフォルトの super() を持ち、親の null パラメータ・コンストラクトを呼び出します。

親コンストラクトを手動で呼び出すと、デフォルトの super() がオーバーライドされます。

super()と "this"()の両方はコンストラクタ・メソッドの最初の行に存在しなければなりません。

-サブクラスは親クラスのメソッドをオーバーライド可能

メソッドのオーバーライド

サブクラスの親にリネームされていないメンバ・メソッドがある場合、この呼び出しはこの時点では何の効果もありません

オブジェクトがメソッドを呼び出すと、まずサブクラスに対応するメソッドがあるかどうかを調べ、サブクラスに存在する場合はサブクラスのメソッドを実行し、サブクラスに存在しない場合は親クラスの対応するメソッドを実行します。コードは以下のようになります:

親:フー

package www.penglei666.com.demo04;

public class Fu {
 public void fn1(){
 System.out.println("親メソッド: fn");
 }
}

サブクラス: Zi

package www.penglei666.com.demo04;

public class Zi extends Fu {
 @Override
 public void fn2() {
 System.out.println("Ziクラスのメソッド: fn");
 }
}

テストクラス:テスト

package www.penglei666.com.demo04;

public class Test {
 public static void main(String[] args) {
 Zi zi = new Zi();
 zi.fn1(); // 出力:親メソッド:fn
 zi.fn2(); // 出力:クラスZiのメソッド:fn
 }
}

サブクラスの親クラスにリネームされたメンバ・メソッドがある場合、このアクセスはメソッド・オーバーライドと 呼ばれる特殊なケースです。

メソッドの上書き: 書き換えまたは書き換えとも呼ばれる上書き効果は、サブクラスに親クラスとまったく同じメソッドがある場合に発生します。宣言はそのままですが、メソッドは再実装されます。

コードは以下の通り:

親:フー

package www.penglei666.com.demo04;

public class Fu {
 public void fn(){
 System.out.println("親メソッド: fn");
 }
}

サブクラス: Zi

package www.penglei666.com.demo04;

public class Zi extends Fu {
 @Override
 public void fn() {
 System.out.println("Ziクラスのメソッド: fn");
 }
}

テストクラス:

package www.penglei666.com.demo04;

public class Test {
 public static void main(String[] args) {
 Zi zi = new Zi();
 zi.fn(); // 出力:クラスZiのメソッド:fn
 }
}

メソッド書き換えの応用

サブクラスは、必要に応じて自分自身に固有のビヘイビアを定義できます。

どちらも親クラスの機能名に従い、サブクラスのニーズに応じて親クラスのメソッドを再実装することで、親クラスを拡張・強化します。

例えば、新しい携帯電話では、以下のコードで発信者番号にアバターを表示する機能が追加されます:

保護者クラス:電話

package www.penglei666.com.demo05;

/**
 * 保護者クラス:電話番号
 */
public class Phone {
 public void sendMessage(){
 System.out.println("テキストメッセージ");
 }
 public void call(){
 System.out.println("コール");
 }
 public void showNum(){
 System.out.println("発信者番号通知");
 }
}

サブカテゴリー

package www.penglei666.com.demo05;

/**
 * サブクラス: NewPhone
 */
public class NewPhone extends Phone {
 //発信者番号関数の親クラスを書き換え、独自の表示名とイメージ関数を追加する。
 public void showNum(){
 //親クラスにすでに存在する関数を呼び出すには、superを使う。
 super.showNum();
 //独自の名前とイメージ表示機能を追加する
 System.out.println("発信者番号通知");
 System.out.println("発信者番号通知");
 }
}

テストクラス:テスト

package www.penglei666.com.demo05;

public class Test {
 public static void main(String[] args) {
 NewPhone np = new NewPhone();
 np.showNum();
 /**
 * 出力結果である:
 * 発信者番号通知
 * 発信者番号通知
 * 発信者番号通知
 */
 }
}

ここでは、super.parent クラス・メンバ・メソッドを使用して、親クラスのメンバ・メソッドが呼び出されることを示します。

-サブクラスの初期化処理

コンストラクタ・メソッドの役割は、メンバ変数を初期化することです。そのため、サブクラスの初期化処理では、まず親クラスの初期化動作を行う必要があります。サブクラスのコンストラクタ・メソッドはデフォルトで super() を持ちます。つまり、親クラスのコンストラクタ・メソッドが呼び出され、親クラスのメンバ変数が初期化されてからサブクラスに渡されます。コードは以下のようになります:

親クラス:

package www.penglei666.com.demo06;

public class Fu {
 public Fu(){
 System.out.println("Fuクラス・コンストラクタの初期化");
 }
}

サブカテゴリー

package www.penglei666.com.demo06;

public class Zi extends Fu {
 public Zi(){
 // super(); スーパーへのデフォルト呼び出し
 System.out.println("Ziクラス・コンストラクタの初期化");
 }
}

テストクラス:

package www.penglei666.com.demo06;

public class Test {
 public static void main(String[] args) {
 Zi zi = new Zi();
 /* 実行結果:
 Fu クラス・コンストラクタの初期化
Ziクラスのコンストラクタ初期化
 */
 }
 
}

- 相続の特徴

特徴1:Javaは単一継承のみをサポートしており、多重継承はサポートしていません。

//クラスは1つの親しか持つことができず、複数の親を持つことはできない。
class C extends A{} 	//ok
class C extends A,B...	//error

特徴2:Javaは複数レベルの継承をサポートしています。

class a {
}
class c extends a {
}
class b extends c {
}

第2章 抽象クラス

-抽象クラスの概要

抽象クラスの起源

クラスを書くとき、クラスの機能を実装するためのメソッドが定義されていることがよくあります。

物事を分析するとき、共通の要素が見つかり、上方抽出が行われます。

しかし、メソッド関数の宣言は同じでも、メソッド関数の主語が異なるという特殊な場合もあります。

しかし、抽出されるのはメソッド宣言だけで、メソッド本体は抽出されません。このメソッドは抽象メソッドです。

として

  • 犬の行動:食事
  • 猫の行動:食事
  • 牛の行動:食べる

犬、猫、牛には共通点があり、共通点を定義する親クラスの上方抽出が可能です。

動物という共通点を抽出します。

犬も猫も牛も、食べるという機能は持っていますが、食べるものは違いますからね。

ここで、動物クラスを記述する際に、具体的に記述できない関数があることがわかり、このような具体的でない関数は、javaのabstractキーワードで修飾したクラスで特定する必要があります。抽象メソッドを定義するクラスもabstractキーワードで変更する必要がある場合、abstractキーワードで変更されたクラスはabstractクラスです

概要

  • 抽象メソッド :メソッド本体を持たないメソッド。
  • 抽象クラス:抽象メソッドを含むクラス。

-抽象クラス、抽象メソッドの定義と使用

抽象化手法:

abstract キーワードを使用してメソッドを変更すると、そのメソッドは抽象メソッドになり、メソッド名だけが含まれ、メソッド本体は含まれません。

フォーマットを定義します:

修飾子 abstract 戻り値の型 メソッド名;

サンプルコード

public abstract void eat();

抽象クラス:

クラスに抽象メソッドが含まれている場合、そのクラスは抽象でなければなりません。

フォーマットを定義します:

public abstract class   { 
 
}

サンプルコード

public abstract class Animal { 
 public abstract void eat();
}

抽象クラスの使用:

抽象クラスを継承したサブクラスは、親クラスのすべての抽象メソッドをオーバーライドしなければなりません。そうでなければ、そのサブクラスも抽象クラスとして宣言しなければなりません。最終的には、その親クラスの抽象メソッドを実装したサブクラスが存在しなければなりません。そうでなければ、最初の親クラスから最終的なサブクラスまでオブジェクトを作成することはできず、意味がなくなってしまいます。

親:動物

package www.penglei666.com.demo07;
public abstract class Animal {
 public abstract void eat();
}

サブクラス: Dog

package www.penglei666.com.demo07;
public class Dog extends Animal {
 @Override
 public void eat() {
 System.out.println("私は骨が大好きだ。");
 }
}

この時点でのメソッドの書き換えとは、親クラスの抽象メソッドをサブクラスが実装し終わることであり、このようなメソッドを書き換える操作をメソッドの実装とも呼びます。

- 注意事項

  1. 抽象クラスはオブジェクトを作成できません。作成するとコンパイルに失敗し、エラーが報告されます。オブジェクトを作成できるのは、抽象クラス以外のサブクラスのオブジェクトだけです。
    • 理解する方法:抽象クラスのオブジェクトを作成し、抽象メソッドを呼び出し、抽象メソッドが具体的なメソッド本体を持っていないと仮定すると、意味がありません。
  2. 抽象クラスはコンストラクタ・メソッドを持つことができます。コンストラクタ・メソッドは、サブクラスがオブジェクトを作成するときに親クラスのメンバを初期化するために使用します。
    • サブクラスはコンストラクタ・メソッドにデフォルトの super() を持ち、親クラスのコンストラクタ・メソッドにアクセスする必要があります。
  3. 抽象クラスはメンバ変数を持つことができます。
    • 理解方法: サブクラスに共通するメンバ変数は、抽象親クラスで定義することができます。
  4. 抽象クラスは必ずしも抽象メソッドを含んでいるとは限りませんが、抽象メソッドを持つクラスは抽象クラスでなければなりません。
    • 抽象メソッドを含まない抽象クラスの目的は、呼び出し元がクラスのオブジェクトを作成しないことです。
  5. 抽象クラスのサブクラスは、抽象親クラスのすべての抽象メソッドをオーバーライドする必要があります。サブクラスも抽象クラスである場合を除きます。
    • 理解方法: すべての抽象メソッドがオーバーライドされていないと仮定すると、クラスには抽象メソッドが含まれている可能性があります。その場合、オブジェクトを作成した後に抽象メソッドを呼び出すのは意味がありません。

第III章 総合的なケース

ケースの要件

IT企業には多くの社員が在籍しており、その社員が担当する業務によって部署が分かれています。研究開発部門は、研究開発内容によってJavaEEエンジニアとAndroidエンジニアに分かれ、保守部門は、保守内容によってネットワーク保守エンジニアとハードウェア保守エンジニアに分かれます。

会社の従業員にはそれぞれ社員番号と名前があり、担当する仕事をすることが期待されています。

仕事内容

  • JavaEEエンジニア:xxxの従業員番号xxxはタオバオのウェブサイトで働いています。

  • アンドロイドエンジニア:○○の社員番号○○はタオバオのモバイルクライアントソフトウェアを開発しています。

  • ネットワーク・メンテナンス・エンジニア:従業員番号 xxx の従業員 xxx が、ネットワークがスムーズかどうかをチェックしています。

  • ハードウェア・メンテナンス・エンジニア:従業員番号xxxの従業員xxxがプリンターを修理しています。

社員システムのすべてのクラスの定義を完了し、説明に従ってクラス間の継承関係を指定してください。XX Engineer クラスのオブジェクト生成を実行し、ジョブメソッド呼び出しを完了してください。

ケーススタディ

上記の各部門の説明に基づいて、以下のような従業員制度図が導き出されます。

従業員情報の記述から、各従業員には従業員番号、氏名、実施する業務があると判断します。そして、これらの共通属性と機能を親クラスに抽出し、業務に関する内容を特定技術者が指定します。

仕事内容

  • JavaEEエンジニア:xxxの従業員番号xxxはタオバオのウェブサイトで働いています。
  • アンドロイドエンジニア:○○の社員番号○○はタオバオのモバイルクライアントソフトウェアを開発しています。
  • ネットワーク・メンテナンス・エンジニア:従業員番号 xxx の従業員 xxx が、ネットワークがスムーズかどうかをチェックしています。
  • ハードウェア・メンテナンス・エンジニア:従業員番号xxxの従業員xxxがプリンターを修理しています。

JavaEE Engineer オブジェクトを作成して、作業メソッドの呼び出しを完了します。

実施コード

従業員カテゴリー: 従業員

public abstract class Employee {
	private String id;// 従業員番号
	private String name; // 従業員氏名

public String getId() {
		returnid;
	}
	publicvoid setId(String id) {
		this.id = id;
	}
	public String getName() {
		returnname;
	}
	publicvoid setName(String name) {
		this.name = name;
	}
	
	//workメソッド
public abstract void work(); 
}

R&D社員クラスの定義 Developer Inherit Employeeクラス Employee

public abstract class Developer extends Employee {
}

メンテナンス社員の定義 クラス Maintainer クラス Employee クラスの継承

public abstract class Maintainer extends Employee {
}

JavaEE エンジニアの定義 R&D Employee クラスの継承、作業メソッドのオーバーライド

public class JavaEE extends Developer {
	@Override
	public void work() {
		System.out.println("従業員番号は " + getId() + " 的 " + getName() + " 社員、タオバオのウェブサイトで働く");
	}
}

Androidエンジニアの定義 研究開発社員クラスを継承し、作業メソッドをオーバーライド

public class Android extends Developer {
	@Override
	public void work() {
		System.out.println("従業員番号は " + getId() + " 的 " + getName() + " タオバオのモバイルクライアントソフトウェアを開発している。");
	}
}

ネットワーク・メンテナンス・エンジニアの定義 メンテナンス社員クラスの継承、作業方法の書き換え

public class Network extends Maintainer {
	@Override
	public void work() {
		System.out.println("従業員番号は " + getId() + " 的 " + getName() + " 従業員、ネットワークの滑らかさをチェックする");
	}
}

ハードウェアの定義 ハードウェア・メンテナンス・エンジニア メンテナンス部門社員クラスの継承、作業メソッドのオーバーライド

public class Hardware extends Maintainer {
	@Override
	public void work() {
		System.out.println("従業員番号は " + getId() + " 的 " + getName() + " 従業員、プリンターを修理中だ。");
	}
}

テスト・クラスで、JavaEE Engineer オブジェクトを作成します。

public class Test {
	public static void main(String[] args) {
		//JavaEEエンジニア社員オブジェクトの作成
		JavaEE ee = new JavaEE();
		//従業員の番号を設定する
		ee.setId("000015");
		//従業員の名前を設定する
		ee.setName(" ");
		//従業員の勤務方法に電話する
		ee.work();
	}
}
Read next