4日目のゴール
4日目のテーマは
「コンストラクタで“1つのクラス”を初期化する」から、“複数のクラスが関わる初期化”に意識を広げること です。
ここまでであなたはすでに、
コンストラクタ=new された瞬間に呼ばれる特別メソッド
初期化=「生まれた直後の状態を整えること」
おかしな値をコンストラクタで弾く・補正する
他のクラス(Profile など)を一緒に new して初期化できる
というところまで来ています。
4日目ではここから一歩進んで、
「1つのオブジェクトだけ」ではなく、「関係するオブジェクト全体の初期状態」を考える
「途中で初期化が止まらない」ように設計する
「使う側(main)から見て、初期化が分かりやすい形」を意識する
これを、少し“アプリっぽい”ログイン設定ミニアプリで固めていきます。
今日の題材:ログイン設定ミニアプリ
現実のイメージからクラスを取り出す
現実のアプリを思い浮かべてみてください。
ユーザーがいる
ユーザーにはログインIDやパスワードがある
ログインに関する設定(ロック回数、2段階認証の有無など)がある
これを Java のクラスにすると、こんな登場人物が見えてきます。
User … ユーザーそのもの
LoginSetting … ログインに関する設定(ロック回数、2段階認証など)
Main … それらを new して、状態を確認する
今日は、この2つのクラスが「一緒にちゃんと初期化される」イメージを作っていきます。
まずは素朴な User と LoginSetting を書いてみる
とりあえず動くけど“弱い”バージョン
最初に、あえて「初期化が弱い」形から始めます。
public class LoginSetting {
int maxFailCount;
boolean twoFactorEnabled;
}
Javapublic class User {
String loginId;
String password;
LoginSetting setting;
}
Javaこれを main で使ってみます。
public class Main {
public static void main(String[] args) {
User user = new User();
user.loginId = "taro";
user.password = "pass123";
LoginSetting s = new LoginSetting();
s.maxFailCount = 5;
s.twoFactorEnabled = true;
user.setting = s;
}
}
Java動きます。
でも、問題が山ほどあります。
User を new しただけでは、何も設定されていない
LoginSetting を new しただけでも、何も設定されていない
設定し忘れてもコンパイルエラーにならない
つまり、
「中途半端な状態の User が簡単に生まれてしまう」
状態です。
4日目では、これをコンストラクタで“ガチッ”と固めていきます。
LoginSetting を“ちゃんと初期化されるクラス”にする
コンストラクタでルールを埋め込む
まずは LoginSetting から整えます。
public class LoginSetting {
int maxFailCount;
boolean twoFactorEnabled;
LoginSetting(int maxFailCount, boolean twoFactorEnabled) {
if (maxFailCount <= 0) {
System.out.println("maxFailCount が 0 以下です。3 にします。");
this.maxFailCount = 3;
} else {
this.maxFailCount = maxFailCount;
}
this.twoFactorEnabled = twoFactorEnabled;
}
void show() {
System.out.println("ログイン失敗上限: " + maxFailCount);
System.out.println("2段階認証: " + (twoFactorEnabled ? "有効" : "無効"));
}
}
Javaここでやっていることを整理します。
コンストラクタで必ず maxFailCount と twoFactorEnabled を受け取る
maxFailCount が 0 以下なら、3 に補正する
twoFactorEnabled はそのまま使う
show で「設定の中身」を表示できるようにしている
これで、
new LoginSetting(5, true) と書いた瞬間に、
「ログイン失敗上限 5・2段階認証あり」の設定が必ずできる
という状態になります。
User に「設定付きで生まれてもらう」
User のコンストラクタで LoginSetting も一緒に初期化する
次に、User を整えます。
public class User {
String loginId;
String password;
LoginSetting setting;
User(String loginId, String password, LoginSetting setting) {
if (loginId == null || loginId.isEmpty()) {
System.out.println("loginId が空です。'guest' にします。");
this.loginId = "guest";
} else {
this.loginId = loginId;
}
if (password == null || password.length() < 4) {
System.out.println("password が短すぎます。'pass' にします。");
this.password = "pass";
} else {
this.password = password;
}
if (setting == null) {
System.out.println("LoginSetting が null です。デフォルト設定を使います。");
this.setting = new LoginSetting(3, false);
} else {
this.setting = setting;
}
}
void show() {
System.out.println("=== ユーザー情報 ===");
System.out.println("ログインID: " + loginId);
System.out.println("パスワード: " + password);
System.out.println("--- ログイン設定 ---");
setting.show();
}
}
Javaここでのポイントを深掘りします。
loginId が null や空文字なら “guest” にする
password が null または 4文字未満なら “pass” にする
setting が null なら、「デフォルト設定」を new して使う
つまり、
「User を new した瞬間に、LoginSetting も必ず“何かしらの意味のある状態”で存在する」
という状態になっています。
main から見た“初期化の分かりやすさ”を確認する
使う側のコードがスッキリしているか?
ここまで整えたクラスを、main から使ってみます。
public class Main {
public static void main(String[] args) {
LoginSetting strongSetting = new LoginSetting(3, true);
User user1 = new User("taro", "pass123", strongSetting);
User user2 = new User("", "a", null);
user1.show();
System.out.println("-----");
user2.show();
}
}
Java実行イメージはこんな感じです。
maxFailCount が 0 以下です。3 にします。 // (もし 0 以下を渡した場合)
loginId が空です。'guest' にします。
password が短すぎます。'pass' にします。
LoginSetting が null です。デフォルト設定を使います。
=== ユーザー情報 ===
ログインID: taro
パスワード: pass123
--- ログイン設定 ---
ログイン失敗上限: 3
2段階認証: 有効
-----
=== ユーザー情報 ===
ログインID: guest
パスワード: pass
--- ログイン設定 ---
ログイン失敗上限: 3
2段階認証: 無効
ここで感じてほしいのは、
main は「どう初期化されるか」の細かいルールを知らなくていい
User と LoginSetting のコンストラクタに、ルールが全部閉じ込められているnew User(...) と書いた瞬間に、「もう使っていい状態」の User が必ず手に入る
という安心感です。
「途中で初期化が止まらない」設計を意識する
null のまま放置されるフィールドを作らない
4日目で特に強く意識してほしいのは、
「new したのに、どこかのフィールドが null のまま」という状態を避ける
ということです。
例えば、こういうコードは危険信号です。
User user = new User("taro", "pass123", null);
// あとでどこかで setting を new するつもり…
user.setting = new LoginSetting(5, true);
Java「あとでやるつもり」が一番危ないです。
書き忘れたら、その User は一生 setting が null のままです。
それに対して、さっきの User のコンストラクタはこうでした。
if (setting == null) {
System.out.println("LoginSetting が null です。デフォルト設定を使います。");
this.setting = new LoginSetting(3, false);
} else {
this.setting = setting;
}
Javaこれにより、
「setting が null の User は絶対に生まれない」
という保証ができます。
初期化の意味を一言で言うなら、
「このクラスのオブジェクトは、こういう“最低限の約束”を守った状態で生まれてくる」
というルールを作ることです。
初期化の“責任の分担”を意識する
どこまでをどのクラスが担当するか?
4日目の例では、こういう分担になっていました。
LoginSetting
自分自身の中身(maxFailCount・twoFactorEnabled)のルールを守る
「0 以下なら 3 にする」などのチェックと補正を担当する
User
自分の loginId・password のルールを守る
LoginSetting が null のときに「デフォルト設定を用意する」責任を持つ
Main
User や LoginSetting を new して、
「どんな組み合わせで使うか」を決めるだけ
中身のチェックや補正には踏み込まない
この「誰がどこまで初期化を担当するか」を意識できると、
コードの見通しが一気によくなります。
4日目のミニアプリ完成形をまとめて眺める
コード全体をもう一度
public class LoginSetting {
int maxFailCount;
boolean twoFactorEnabled;
LoginSetting(int maxFailCount, boolean twoFactorEnabled) {
if (maxFailCount <= 0) {
System.out.println("maxFailCount が 0 以下です。3 にします。");
this.maxFailCount = 3;
} else {
this.maxFailCount = maxFailCount;
}
this.twoFactorEnabled = twoFactorEnabled;
}
void show() {
System.out.println("ログイン失敗上限: " + maxFailCount);
System.out.println("2段階認証: " + (twoFactorEnabled ? "有効" : "無効"));
}
}
Javapublic class User {
String loginId;
String password;
LoginSetting setting;
User(String loginId, String password, LoginSetting setting) {
if (loginId == null || loginId.isEmpty()) {
System.out.println("loginId が空です。'guest' にします。");
this.loginId = "guest";
} else {
this.loginId = loginId;
}
if (password == null || password.length() < 4) {
System.out.println("password が短すぎます。'pass' にします。");
this.password = "pass";
} else {
this.password = password;
}
if (setting == null) {
System.out.println("LoginSetting が null です。デフォルト設定を使います。");
this.setting = new LoginSetting(3, false);
} else {
this.setting = setting;
}
}
void show() {
System.out.println("=== ユーザー情報 ===");
System.out.println("ログインID: " + loginId);
System.out.println("パスワード: " + password);
System.out.println("--- ログイン設定 ---");
setting.show();
}
}
Javapublic class Main {
public static void main(String[] args) {
LoginSetting strongSetting = new LoginSetting(3, true);
User user1 = new User("taro", "pass123", strongSetting);
User user2 = new User("", "a", null);
user1.show();
System.out.println("-----");
user2.show();
}
}
Javaこのコードを見て、
「User を new した瞬間に、どんな状態の User が生まれるか」
「LoginSetting を new した瞬間に、どんな状態の設定が生まれるか」
を、自分の言葉で説明できたら、4日目はかなりいい感じです。
4日目で絶対に押さえてほしい本質
今日いちばん大事なのは、
「初期化=“オブジェクト単体”ではなく、“関係するオブジェクト全体”の状態を整えること」
という視点を持てたかどうかです。
コンストラクタでやるべきことは、
必須の情報を必ず受け取るようにする
おかしな値が来たら、その場でチェックして補正する
null のまま放置されるフィールドを作らない
必要な他のオブジェクトも、そこで new しておく
そして、
new した瞬間に、もう安心して使える状態にしておく途中で初期化が止まらないようにする
この感覚が入っていれば、
コンストラクタはもう「文法」ではなく、
“オブジェクトの安全な生まれ方をデザインする道具” になっています。
5日目以降は、
この初期化の考え方を、設定クラス・ゲームのステータス・アプリ全体の構成など、
もっと大きな単位に広げていくと、一気に設計力が伸びていきます。


