パパの勉強部屋

勉強の記録をつけています。Java、ネットワーク、Excel、etc.

スッキリわかるJava入門 第9章 さまざまなクラス機構②

第9章 さまざまなクラス機構②

コンストラクタ 生まれたてのインスタンスの状態

  • クラスを用いたプログラミングを始めると、ある「めんどうくささ」を感じる。

public class Main {
  public static void main(String[] args) {
    Hero h1 = new Hero();
    h1.name = "ミナト";
    h1.hp = 100;
    Hero h2 = new Hero();
    h2.name = "アサカ";
    h2.hp = 100;
    Wizard w = new Wizard();
    w.name = "スガワラ";
    w.hp = 50;
    w.heal(h1);    // ミナトを回復させる(HP: 100 -> 110)
    w.heal(h2);    // アサカを回復させる(HP: 100 -> 110)
    w.heal(h2);    // アサカを回復させる(HP: 110 -> 120)
  }
}

  • newでインスタンスを生成した直後に、必ずフィールドの初期代入をしている。
  • newで生み出されたばかりのインスタンスのフィールド(nameやhp)には、まだ何も入っていないから。
フィールドの初期値
int型,short型,long型 等の数値の型 0
char型(文字) \u0000
boolean型 false
int[]などの配列型 null
String型などのクラス型 null

フィールド初期値を自動設定する

  • 実際の開発現場において、大規模な開発を行う場合、1人ですべてを開発することは、まずあり得ない。
  • クラスを作るにあたっては、自分以外の開発者がHeroクラスを利用することも考えておかなければならない。
  • その開発者が正しくHPに100を代入してくれるとは限らない。
  • 「勇者のHPが100であること」は、勇者自身に関する事で、Heroクラスの開発者が一番よく知っていること。(Heroクラス側で責任を持つこと)
  • javaでは「インスタンスが生まれた直後に自動実行される処理」をプログラミングできるようになっている。

public class Hero {
  String name;
  int hp;
  Sword sword;
  public void attack() {
    System.out.println(this.name + "は攻撃した!");
    System.out.println("敵に5ポイントのダメージをあたえた!");
  }
  public Hero() {
    this.hp = 100;    // hpフィールドを100で初期化
  }
}

  • 9行目のHero()メソッドは、このクラスがnewされると、newと同時にメソッド内部で定義しておいた処理が自動的に行われる。
  • このようなメソッドをコンストラクタ(constructor)と呼ぶ。
  • newされると自動的に実行されてHPに100が代入される。そのためmainメソッド側でHPに初期値を代入する必要はない。

コンストラクタの定義条件

  • 「自動実行されるメソッドの条件」を満たすように定義する。
コンストラクタとみなされる条件
  • ①メソッド名がクラス名と完全に等しい
  • ②メソッド宣言に戻り値が記述されていない(voidもダメ)

コンストラクタに情報を渡す

  • HPフィールドは「100」という固定の値で初期化すればよいため、単純なコンストラクタで済んだ。
  • 勇者の名前は生み出すインスタンスによって異なる。
  • コンストラクタが「毎回異なる追加情報」を引数で受け取れるように宣言する。

public class Hero {
  String name;
  int hp;
  Sword sword;
  public void attack() {
    System.out.println(this.name + "は攻撃した!");
    System.out.println("敵に5ポイントのダメージをあたえた!");
  }
  public Hero(String name) {
    this.hp = 100;
    this.name = name;    // 引数の値でnameフィールドを初期化
  }
}

  • このようなHeroクラスを利用する場合は、コンストラクタが実行される際に渡して欲しい引数をnewする際に指定する。

public class Main {
  public static void main(String[] args) {
    Hero h = new Hero("ミナト");
    
    System.out.println(h.hp);
    System.out.println(h.name);
  }
}

2つ以上の同名コンストラクタを定義する

  • 現在のHeroクラスには「文字列引数を1つ受け取るコンストラクタ」が定義されている。
  • このコンストラクタを作ったことによって、インスタンスを生成するときには、名前を指定する必要が生じた。(new Hero();を実行するとエラーとなる)
  • この問題は「引数を受け取らないコンストラクタ」も同時に定義することで解決できる。

public class Hero {
  String name;
  int hp;
  Sword sword;
  public void attack() {
    System.out.println(this.name + "は攻撃した!");
    System.out.println("敵に5ポイントのダメージをあたえた!");
  }
  public Hero(String name) {
    this.hp = 100;
    this.name = name;    // 引数の値でnameフィールドを初期化
  }
  public Hero() {
    this.hp = 100;
    this.name = "ダミー";
  }
}

  • 「同じ名前だが引数が異なるメソッドを複数定義」するオーバーロードは、コンストラクタでも可能。

public class Main {
  public static void main(String[] args) {
    Hero h1 = new Hero("ミナト");
    System.out.println(h1.name);
    Hero h2 = new Hero();
    System.out.println(h2.name);
  }
}

複数のコンストラクタが定義されていた場合
  • newするときに渡した引数の型・数・順番に対応するコンストラクタが動作する。(複数のコンストラクタが定義されていても、1つだけしか動作しない)

暗黙のコンストラク

  • コンストラクタが定義されていなかった頃のHeroクラスは「new Hero();」だけで生成できたのに、引数ありコンストラクタを定義しただけで同様のことが不可能になった。
  • Javaでは、すべてのクラスはインスタンス化に際して必ず何らかのコンストラクタを実行することになっている。
  • 本来すべてのクラスは「引数を何もとらず、何も処理をしないコンストラクタ」でよいので、最低でも1つ以上のコンストラクタ定義を持っていなければならない。
  • わざわざダミーでコンストラクタを定義するのはめんどうなので、Javaでは以下の特例を設けている。
コンストラクタの特例
  • クラスに1つもコンストラクタが定義されていない場合に限って、「引数なし、処理内容なし」のコンストラクタ(デフォルトコンストラクタ)の定義がコンパイル時に自動的に追加される。

他のコンストラクタを呼び出す

  • 2つ以上のコンストラクタを定義していると、そのうち「重複する処理」が気になる。
  • そこで思いつくのが、コンストラクタ②の中で、コンストラクタ①を呼び出す方法。
  • Javaではコンストラクタを直接呼び出すことができないため、以下のコードはコンパイルエラーとなる。

public class Hero {
  String name;
  int hp;
  Sword sword;
  public void attack() {
    System.out.println(this.name + "は攻撃した!");
    System.out.println("敵に5ポイントのダメージをあたえた!");
  }
  public Hero(String name) {    // コンストラクタ①
    this.hp = 100;
    this.name = name;
  }
  public Hero() {    // コンストラクタ②
    this.Hero("ダミー");
  }
}

別コンストラクタの呼び出しに関するルール
  • 「コンストラクタの先頭で、別のコンストラクタを専用の文法を用いて呼び出す場合に限って」特別に許される。
  • 「this.クラス名(引数);」と記述する事はできない。
  • その代わりに「this(引数);」と記述する。

public class Hero {
  String name;
  int hp;
  Sword sword;
  public void attack() {
    System.out.println(this.name + "は攻撃した!");
    System.out.println("敵に5ポイントのダメージをあたえた!");
  }
  public Hero(String name) {    // コンストラクタ①
    this.hp = 100;
    this.name = name;
  }
  public Hero() {    // コンストラクタ②
    this("ダミー");
  }
}