2日目のゴール
2日目のテーマは
「コンストラクタで“どこまで初期化するか”を自分で設計できるようになること」 です。
1日目であなたはすでに、
コンストラクタ=new された瞬間に呼ばれる特別メソッド
初期化=「生まれた直後の状態を整えること」
というイメージをつかみました。
2日目ではここから一歩進んで、
「どんな状態で生まれてきてほしいか」を自分で決める
「いくつかのパターンで生まれさせる」ことを考える
「初期化をバラバラに書かず、コンストラクタに集める」感覚を強くする
ここを、具体的な例を通して固めていきます。
1日目の Player をもう一度思い出す
「生まれた瞬間にちゃんとした状態になる」感覚
1日目の Player はこんな感じでした。
public class Player {
String name;
int hp;
Player(String name) {
this.name = name;
this.hp = 100;
}
void showStatus() {
System.out.println("名前: " + name);
System.out.println("HP : " + hp);
}
}
Javapublic class Main {
public static void main(String[] args) {
Player p = new Player("勇者");
p.showStatus();
}
}
Javaここでのポイントは、
new Player("勇者") と書いた瞬間に
「名前が勇者で、HP が 100 の Player」が必ず生まれる
という“安心感”でした。
2日目では、この「安心感」をもっと設計できるようにしていきます。
「初期化をコンストラクタに集める」と何が嬉しいか
バラバラ代入と比べてみる
まず、コンストラクタを使わないパターンをもう一度見てみます。
public class Player {
String name;
int hp;
}
Javapublic class Main {
public static void main(String[] args) {
Player p = new Player();
p.name = "勇者";
p.hp = 100;
p.hp = p.hp - 30;
}
}
Javaこの書き方だと、
name を設定し忘れてもコンパイルエラーにならない
hp を設定し忘れても、そのまま 0 で動いてしまう
つまり、「中途半端な状態の Player」が簡単に生まれてしまいます。
一方、コンストラクタに初期化を集めるとこうなります。
public class Player {
String name;
int hp;
Player(String name) {
this.name = name;
this.hp = 100;
}
}
Javapublic class Main {
public static void main(String[] args) {
Player p = new Player("勇者");
}
}
Javaこの形だと、
名前を渡さないとコンパイルエラー
HP を設定し忘れることもない(コンストラクタの中で必ず 100 にしている)
という「強いルール」を作れます。
ここでの本質は、
「初期化をコンストラクタに集めると、“変な状態のオブジェクト”が生まれにくくなる」
ということです。
「初期化のパターンが複数ある」場合を考えてみる
例:HP を変えたいとき、どうする?
ゲームを少しだけリアルにしてみます。
普通のプレイヤーは HP 100
上級者プレイヤーは HP 200
こういう仕様にしたくなったとします。
やり方はいくつかありますが、
まず「悪い例」から見てみます。
public class Main {
public static void main(String[] args) {
Player normal = new Player("初心者");
Player advanced = new Player("上級者");
advanced.hp = 200; // 後から書き換える
}
}
Java動きます。
でも、これだと「上級者の HP を 200 にする」というルールが main に散らばります。
別の場所で上級者を作るとき、また同じことを書かないといけない
書き忘れると、上級者なのに HP 100 のままになる
こういう「ルールがバラバラに散らばる」状態は、あとで必ずしんどくなります。
コンストラクタを“複数用意する”という考え方
コンストラクタのオーバーロード
ここで出てくるのが、
「コンストラクタを複数定義する(オーバーロード)」 という考え方です。
Player に、こういうコンストラクタを追加してみます。
public class Player {
String name;
int hp;
Player(String name) {
this.name = name;
this.hp = 100;
}
Player(String name, int hp) {
this.name = name;
this.hp = hp;
}
void showStatus() {
System.out.println("名前: " + name);
System.out.println("HP : " + hp);
}
}
Javaこれで、こういう使い方ができます。
public class Main {
public static void main(String[] args) {
Player normal = new Player("初心者"); // HP 100
Player advanced = new Player("上級者", 200); // HP 200
normal.showStatus();
advanced.showStatus();
}
}
Javaここでのポイントは、
Player(String name) と Player(String name, int hp)
同じ名前(Player)だけど、引数の数が違うコンストラクタを2つ定義している
呼び出し側は、渡す引数の数で「どのコンストラクタを使うか」を選べる
ということです。
これにより、
「普通のプレイヤーは名前だけ渡す」
「HP を指定したいときは、名前と HP を渡す」
という2パターンの“生まれ方”を、
コンストラクタで表現できるようになります。
初期化ロジックを“1か所にまとめる”テクニック
this(…) を使ってコンストラクタ同士をつなぐ
さっきの Player には、ちょっとだけ気になる点があります。
Player(String name) {
this.name = name;
this.hp = 100;
}
Player(String name, int hp) {
this.name = name;
this.hp = hp;
}
Javaname を代入するコードが、2か所に重複しています。
こういう「同じことを2回書く」は、あとで変更するときにミスの元になります。
そこで使えるのが、this(...) を使って、コンストラクタから別のコンストラクタを呼ぶ というテクニックです。
こう書き換えられます。
public class Player {
String name;
int hp;
Player(String name) {
this(name, 100);
}
Player(String name, int hp) {
this.name = name;
this.hp = hp;
}
void showStatus() {
System.out.println("名前: " + name);
System.out.println("HP : " + hp);
}
}
Javaここで何が起きているか、丁寧に追ってみます。
Player(String name) が呼ばれたとき
最初の行 this(name, 100); が実行される
これは「同じクラスの別のコンストラクタ Player(String name, int hp) を呼ぶ」という意味
結果として、「名前と HP を指定するコンストラクタ」が呼ばれ、this.name = name; this.hp = hp; が実行される
つまり、
「最終的な初期化ロジックは、Player(String name, int hp) に1か所だけ書いてある」
という状態になります。
これがめちゃくちゃ大事です。
「初期化の中心」をどこに置くかを意識する
ルールは1か所に集めると強くなる
さっきの Player をもう一度見ます。
Player(String name) {
this(name, 100);
}
Player(String name, int hp) {
this.name = name;
this.hp = hp;
}
Javaここでの設計はこうです。
「Player の本当の初期化ルール」は Player(String name, int hp) に書く
「HP を 100 にしたいだけのとき」は、そこに委ねる
このように、
「初期化の中心となるコンストラクタ」を1つ決めて、他のコンストラクタはそこに寄せる
という考え方は、現場のコードでもよく使われます。
もし将来、「初期 HP を 150 にしたい」となったら、Player(String name, int hp) の中身を変えるだけで済みます。
Player(String name, int hp) {
this.name = name;
this.hp = hp + 50; // 例えばこういう仕様にしてもいい
}
JavaPlayer(String name) のほうは、this(name, 100); のままでOKです。
「中心」が1か所にあるからこそ、変更に強くなります。
2日目のミニアプリ:難易度付き Player
仕様を言葉で決める
Player クラスを作る
Player は名前と HP を持つ
普通のプレイヤーは HP 100
「難易度ハード」のプレイヤーは HP 50
main で、2種類のプレイヤーを作って状態を表示する
これをコンストラクタで表現してみます。
public class Player {
String name;
int hp;
Player(String name) {
this(name, 100);
}
Player(String name, int hp) {
this.name = name;
this.hp = hp;
}
void showStatus() {
System.out.println("名前: " + name);
System.out.println("HP : " + hp);
}
}
Javapublic class Main {
public static void main(String[] args) {
Player normal = new Player("ノーマル勇者");
Player hard = new Player("ハード勇者", 50);
normal.showStatus();
System.out.println("-----");
hard.showStatus();
}
}
Java実行イメージはこんな感じです。
名前: ノーマル勇者
HP : 100
-----
名前: ハード勇者
HP : 50
ここで感じてほしいのは、
「難易度の違い」という“ゲームのルール”が、
main にバラバラに書かれているのではなく、
コンストラクタの形として表現されている
ということです。
今日いちばん大事な“頭の中の整理”
初期化=「どんな状態で生まれてきてほしいか」を決めること
2日目で絶対に持って帰ってほしいのは、この感覚です。
初期化とは
「このクラスのオブジェクトは、こういう状態で生まれてきてほしい」という“約束”を決めること。
コンストラクタとは
その約束をコードとして書く場所。
new された瞬間に必ず呼ばれる、“初期化専用の特別メソッド”。
複数のコンストラクタを持つ意味
「生まれ方のパターン」を増やすこと。
引数の違いで、「どう初期化するか」を選べるようにする。
this(…) を使う意味
初期化ロジックの“中心”を1か所に集めるため。
同じことを何度も書かず、変更に強いコードにするため。
そして、
「このクラスは、どんな状態で生まれてきてほしい?」
「そのパターンはいくつある?」
「初期化の中心はどのコンストラクタに置く?」
と自分に問いかけながらコンストラクタを書く——
この思考ができていたら、2日目は完璧です。
3日目以降は、
コンストラクタと他のクラス(例えば装備・ステータス・設定クラスなど)を組み合わせて、
「複数のオブジェクトをまとめて初期化する」世界に進んでいけます。


