0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Java】継承(Inheritance)

Last updated at Posted at 2020-11-04

継承とは

  • 元になるクラスのメンバーを引き継ぎ、新たなクラスを定義すること
  • 基底クラス(スーパークラス、親クラス):継承元
  • 派生クラス(サブクラス、子クラス):継承してできたクラス
  • 機能の共通した部分を切り出して差分だけを書く仕組み
  • cf: 委譲:特定の役割を別のクラスとして切り出しそのインスタンスをフィールドとして保存

継承の使い方

  • class 派生クラス extends 基底クラス{}
  • extends省略するとObjectクラスを継承したことになる
  • 現在のクラスで要求されたメンバーを検索し、存在しない場合上位クラスで定義されたメンバーを呼び出す!
    • 基底、派生クラス両方にあるメソッドは派生クラスを優先して呼ぶ
  • 注意:
    • Javaでは基底クラスは1個だけ(多重継承認不可)
    • 基底クラスで定義されたメンバーを派生クラスで削除できない
    • =派生クラスは基底クラスの全性質を含む
Person.java
public class Person {
  public String name;
  public int age;

  public String show() {
    return String.format("%s(%d歳)です。", this.name, this.age);
  }
}
BusinessPerson.java
//Personを継承したBusinessPersonクラス作成
public class BusinessPerson extends Person {
  //派生クラス独自のworkメソッド定義
  public String work() {
    return String.format("%d歳の%sは今日も元気に働きます。", this.age, this.name);
  }
}
public class InheritBasic {
  public static void main(String[] args) {
    //BusinessPersonのみ呼び出す
    var bp = new BusinessPerson();
    bp.name = "佐藤一郎";
    bp.age = 30;
    //showメソッドがbpクラスのメンバーのように呼べる!
    System.out.println(bp.show()); //佐藤一郎(30歳)です。
    System.out.println(bp.work());  //30歳の佐藤一郎は今日も元気に働きます。
  }
}
  • ポイント
    • 親クラスと子クラスにis-aの関係があれば継承をつかう
      • is-a:SubClass is a SuperClass
        • Person⊃BusinessPerson
        • BusinessPersonがPersonに全て含まれる関係
      • 派生クラスは基底クラスの特化
      • 基底クラスは派生クラスの汎化

フィールドの隠蔽

  • 継承ではメンバーによって変更可否が異なる
    • オーバーライド:メソッドが可能
    • 隠蔽:フィールド、入れ子クラス/インターフェースが可能

隠蔽

  • 基底クラスの同名フィールドを派生クラスで定義した場合、基底クラスのフィールドが見えなくなる
  • データ型が異なっても名前が同じなら隠蔽される
  • 予約変数**super**を用いることで隠蔽フィールドにアクセス可能
  • 注意:基底クラスのフィールドがprivateではアクセス不可能
Person.java
import java.time.ZonedDateTime;
public class Person {
  public String name;
  public ZonedDateTime birth = ZonedDateTime.now();
}
BusinessPerson.java
import java.time.LocalDateTime;
public class BusinessPerson extends Person {
  //基底クラスのbirthフィールドを隠蔽
  public LocalDateTime birth = LocalDateTime.now();
  public void show() {
    //隠蔽フィールドにアクセス
    System.out.println(super.birth);
  }
}
public class HideBasic {

  public static void main(String[] args) {
    var bp = new BusinessPerson();
    //BusinessPerson.birthを表示
    System.out.println(bp.birth); 
    bp.show();
    //Person.birthフィールドを表示
    Person p = new BusinessPerson();
    System.out.println(p.birth);
  }
}

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

  • 基底クラスで定義されたメソッドを派生クラスで上書き(再定義)
  • 見るのは名前のみではない(隠蔽と異なる)
    • メソッド名:完全一致
    • 仮引数:データ型/個数一致
      • 基底の(CharSequence s)を派生の(String s)でオーバーライド不可
    • 戻り値:型一致/派生型
    • アクセス修飾子:一致/基底型より緩い
      • public>protected>無指定>private
      • privateはオーバーライド不可
    • throw句(例外):一致/派生型
  • →**@Overrideアノテーション**を使えばOK
    • 基底クラスのメソッドをオーバーライドしていることを明示的に宣言
public class Person {
  public String name;
  public int age;

  public String show() {
    return String.format("%s(%d歳)です。", this.name, this.age);
  }
}
public class BusinessPerson extends Person {
  public BusinessPerson() {}
  //基底クラスの同名showメソッドを上書き
  @Override
  public String show() {
    return String.format("会社員の%s(%d歳)です。", this.name, this.age);
  }

  public String work() {
    return String.format("%d歳の%sは今日も元気に働きます。", this.age, this.name);
  }
}

基底クラス参照(super)

  • superによるメソッド呼び出しは派生クラスのメソッド定義の先頭
//BusinessPersonクラスを継承
public class EliteBusinessPerson extends BusinessPerson {
  @Override 
  //基底クラスのworkメソッドを呼び出し、独自の処理を追加する

  public String work() {
    //派生クラスの先頭でsuperメソッド呼び出し
    var result = super.work();
    return String.format("%sいつでも笑顔で!", result);
  }
}
public class InheritBaseCall {
  public static void main(String[] args) {
    var ebp = new EliteBusinessPerson();
    ebp.name = "山田太郎";
    ebp.age = 35;
    System.out.println(ebp.work()); //35歳の山田太郎は今日も元気に働きます。いつでも笑顔で!
  }
}

派生クラスのコンストラクタ

  • コンストラクターはメソッドと同じようには引き継がれない
  • 継承関係にあるクラスでは上位クラスから順にコンストラクタが呼ばれ、最終的に現在のクラスのコンストラクタが呼ばれる
    • 基本クラスの初期化は基本クラスのコンストラクタ
    • 派生クラスの初期化は派生クラスのコンストラクタ
      • 1.全フィールドを規定値で初期化
      • 2.基底クラスの初期化ブロック実行
      • 3.基底クラスのコンストラクタ実行
      • 4.派生クラスの初期化ブロック実行
      • 5.派生クラスのコンストラクタ実行
MyParent.java
public class MyParent {
  public MyParent() {
    System.out.println("親です。");
  }
}
MyChild.java
public class MyChild extends MyParent {
  public MyChild() {
    System.out.println("子です。");
  }
}
public class InheritConstruct {
  public static void main(String[] args) {
    var c = new MyChild(); //親です。 子です。
  }
}
  • 上位クラスで暗黙的に呼ばれるのは引数なしコンストラクタのみ
    • 明示的にコンストラクタを定義(=引数付き)した場合、デフォルトコンストラクタは自動生成されない
    • →引数付きコンストラクタを、派生クラスのコンストラクタからsuperで呼ぶ
    • 必ずコンストラクタの先頭文で呼ぶ
//上位クラス(引数付きコンストラクタ)
public class MyParent {
  public MyParent(String name) {
    System.out.printf("%sの親です。\n", name);
  }
}
public class MyChild extends MyParent {
  public MyChild(String name) {
    //派生クラスのコンストラクタから上位クラスの引数なしコンストラクタを呼ぶ
    super(name);
    //基底クラス→派生クラスの順でコンストラクタが呼ばれる
    System.out.printf("子の%sです。\n", name); //山田太郎の親です。\n山田太郎の子です。
  }
}
public class InheritConstruct {
  public static void main(String[] args) {
    var c = new MyChild("山田太郎");
  }
}

継承/オーバーライド禁止

  • 継承可能クラスの実装や修正は派生クラスへの影響も考慮するべき
  • 継承/オーバーライドを想定しないクラス/メソッドの場合、final修飾子で禁止する
public class Person {
  String name;
  int age;
  public Person() {}
  //showメソッドをオーバーライド禁止
  public final String show() {
    return String.format("%s(%d歳)です。", this.name, this.age);
  }
}
//BusinessPersonクラスを継承禁止
public final class BusinessPerson extends Person {
  public String intro() {
    return "会社員です。";
  }
}

参照型の型変換

  • 型同士が継承/実装の関係にあること

アップキャスト

  • 派生クラスから基底クラスへの変換のこと
    • BusinessPerson型をPerson型に変換
  • 派生クラスは基底クラスのメンバを全部含んでいる
  • 派生クラスのインスタンスは基底クラスのインスタンスとして利用可能
  • この場合変換の型はPerson、オブジェクトの型はBusinessPerson(後述の 変数の型/オブジェクトの型 参考)
//アップキャスト
public class CastUp {
  public static void main(String[] args) {
    //BusinessPersonオブジェクトを基底クラスPerson型の変数に代入して変換
    Person bp = new BusinessPerson();
    bp.name = "山田太郎";
    bp.age = 20;
    System.out.println(bp.show());
  }
}

ダウンキャスト

  • 基底クラスから派生クラスへの変換のこと
    • Person型をBusinessPerson型に変換
  • 基底クラスは常に派生クラスとして振る舞えない
  • キャスト構文で明示的に型変換が必要
  • Person p = new BusinessPerson();
  • BusinessPerson bp = (BusinessPerson)p;

変数の型/オブジェクトの型

  • PersonクラスのshowメソッドをBusinessPersonでオーバーライド
  • しかし以下コードではBusinessPersonクラスのworkメソッドを呼べない
    • 変数pはPerson型の変数
      • 変数pがworkメソッドを呼び出せない
    • 変数pのオブジェクトの型(実体)はBusinessPerson
      • BusinessPersonのshowメソッドは呼び出される
BusinessPerson.java
public class BusinessPerson extends Person {
  public BusinessPerson() {}

  @Override
  public String show() {
    return String.format("会社員の%s(%d歳)です。", this.name, this.age);
  }

  public String work() {
    return String.format("%d歳の%sは、働きます。", this.age, this.name);
  }
}
public class TypeDifference {

  public static void main(String[] args) {
    Person p = new BusinessPerson();
    p.name = "山田太郎";
    p.age = 30;
    // System.out.println(p.work()); //エラー
    System.out.println(p.show()); //会社員の山田太郎(30歳)です。
  }
}
  • つまり、、、
    • キャストは変数の型を差し替える
      • BusinessPerson変数型をPerson変数型に変換
    • オブジェクトの型(実体)はキャストしても変化しない
    • 呼び出されるメソッドはオブジェクトの型が決める
      • PersonではBusinessPersonの振る舞いができない

クラスメソッド/フィールドの隠蔽

  • クラスメソッドはクラス名.メソッド名(...)で呼ぶ
    • 変数の型/オブジェクトの型が存在しない
    • 常に指定クラスのメソッドが呼ばれるのでオーバーライド概念がない
  • フィールドの選択は変数の型で決まる
public class HideBasic {
  public static void main(String[] args) {
    var bp = new BusinessPerson();
    System.out.println(bp.birth);
    bp.show();
    //変数pはPerson型
    Person p = new BusinessPerson();
    //PersonクラスのbirthフィールドはZonedDateTime型
    System.out.println(p.birth); //ZonedDateTime型
  }
}

型判定

  • ダウンキャスト時にはオブジェクトの型チェックが必要
  • 例えばこんなことが。。。
    • BusinessPerson/StudentはPerson派生クラスである
      • Person p = new BusinessPerson(); //OK
      • BusinessPerson bp = (BusinessPerson)p; //OK
      • Student st = (Student)p; //NG
    • 変数pの実体(オブジェクト型)はBusinessPerson
    • コンパイル時はPersonとBusinessPerson/Student継承関係しか分からないのでコンパイルエラーにならないが、実行時にエラーになる!
  • →**instanceof演算子を使う**
    • 変数に格納されたオブジェクト型が指定型に変換できる場合true
//型チェック
if(p instanceof Student){
    Student st = (Student)p;
    //正しくキャストできたときの処理
}

型取得

  • getClassメソッドでオブジェクトの型を取得
  • 変数の型によらずオブジェクトの型を取得
public class Main {
    public static void main(String[] args) {
    Person p1 = new Person();
    System.out.println(p1.getClass()); //class Person
    Person p2 = new BusinessPerson();
    System.out.println(p2.getClass()); //class BusinessPerson
  }
}

委譲

  • 継承は基底/派生クラスが密結合
  • 継承を使うタイミング
    • 基底/派生クラスがis-a関係
      • リフコフの置換原則:基底クラスの変数にその派生クラスのインスタンスを代入してもコードの妥当性が損なわれない
    • 拡張を前提とし、その旨を文書化している
  • 継承が不適な例
import java.util.Random;

public class Roulette extends Random {
//ルーレット上限
private int bound;

  public Roulette(int bound) {
    this.bound = bound;
  }
  //boundフィールド上限とする値を取得し乱数生成
  @Override
  public int nextInt() {
    return nextInt(this.bound);
  }
  //他の不要メソッドは無効化している(リフコフの置換原則に反する)
  @Override
  public boolean nextBoolean() {
    throw new UnsupportedOperationException();
  }

  @Override
  public long nextLong() {
    throw new UnsupportedOperationException();
  }
}
import java.util.Random;

public class RouletteClient {
  public static void main(String[] args) {
    Random rou = new Roulette(10);
    System.out.println(rou.nextBoolean()); //UnsupportedOperationException
  }
}
  • 委譲では再利用したい機能をもつオブジェクトを現クラスのフィールドとして取り込む
    • 委譲はインスタンス同士の動的な関係である
  • RouletteクラスのrandomフィールドにRandomオブジェクトを保持(has)し必要に応じてRandomクラスのメソッドを利用できる
    • has-a関係という
  • メリット:
    • クラス同士の関係が緩くなる
    • publicメンバーを利用するので内部実装に左右しない
    • フィールドでインスタンス保持するので関係が固定しない
      • 後から委譲先変更可能
      • 複数クラスに処理委譲可能
      • インスタンス単位で委譲先変更可能
import java.util.Random;

public class Roulette {
  private int bound;
  //以上先のオブジェクトをフィールドに保持
  private Random random = new Random();

  public Roulette(int bound) {
    this.bound = bound;
  }
  //必要に応じて処理を委譲
  public int nextInt() {
    return this.random.nextInt(this.bound);
  }
}
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?