インスタンス生成の全体像
インスタンス生成は「クラスという設計図から、具体的な“もの”をメモリ上に作る」行為です。Java では new クラス名(引数…) を呼ぶと、必要なメモリが確保され、フィールドが初期化され、コンストラクタが実行されます。生成されたオブジェクトは参照(変数)に入れて使い、不要になればガベージコレクション(GC)が自動で回収します。
コンストラクタと new の基本
コンストラクタの定義と呼び出し
コンストラクタは「初期状態を確立するメソッドの特別版」です。戻り値型は書かず、クラス名と同名で定義します。
public final class User {
private final String id;
private String name;
public User(String id, String name) { // コンストラクタ
if (id == null || id.isBlank()) throw new IllegalArgumentException("id");
this.id = id;
this.name = name == null ? "" : name.trim();
}
}
Java呼び出しは new User("U-1", " Taro ") のように行い、生成直後から「検証された正しい状態」を持ちます。
オーバーロードと共通化(this/super)
複数の初期化パターンがあるなら、コンストラクタをオーバーロードし、共通処理へ集約します。
public User(String id) { this(id, ""); } // this(...) で別コンストラクタへ委譲
Java継承時は親の初期化を super(...) で先に呼びます(コンストラクタの先頭で必須)。
public class Employee extends User {
private final int grade;
public Employee(String id, String name, int grade) {
super(id, name); // 先に親の初期化
this.grade = grade;
}
}
Java初期化の順序とフィールドの扱い(重要ポイントの深掘り)
new の裏側で起きること
- メモリ確保(フィールド分)
- フィールドのデフォルト初期化(数値は0、参照はnull、booleanはfalse)
- フィールド初期化式の実行(書いていれば)
- 親→子の順でコンストラクタ実行(
super(...)→ 自分)
この順序を理解すると「null のまま使う」「未初期化」の事故を避けられます。
public final class Counter {
private int value = 10; // 初期化式
public Counter() { value++; } // 11 になる
}
Java不変フィールド(final)と安全性
final フィールドは「コンストラクタ完了までに一度だけ代入」され、以後変更できません。並行性やテストで強力な安全性を提供します。
public final class Token {
private final String value;
public Token(String v) { this.value = v; } // 以後不変
public String value() { return value; }
}
Java便利な生成パターンと注意点
ファクトリメソッドとバリデーションの集中
生成規則が複雑なら「名前付きの生成メソッド」に集約します。意図が伝わり、例外方針も一箇所で管理できます。
public final class Email {
private final String value;
private Email(String v) { this.value = v; }
public static Email of(String raw) {
var v = normalize(raw);
if (!isValid(v)) throw new IllegalArgumentException("invalid email");
return new Email(v);
}
}
Javaビルダーパターン(多引数・可読性の向上)
引数が多い/オプションだらけなら、順不同で設定できるビルダーが有効です。
public final class Mail {
private final String to, subject, body;
private Mail(String to, String subject, String body) { /* ... */ }
public static class Builder {
private String to, subject, body;
public Builder to(String v) { this.to = v; return this; }
public Builder subject(String v) { this.subject = v; return this; }
public Builder body(String v) { this.body = v; return this; }
public Mail build() { return new Mail(to, subject, body); }
}
}
// 生成
var mail = new Mail.Builder().to("a@b").subject("Hi").body("Hello").build();
Javanew の特殊形(配列/匿名クラス/内部クラス)
- 配列は
new 型[長さ]またはリテラル{...}で作成します。
int[] a = new int[3]; // 0,0,0
String[] s = { "A", "B" }; // リテラル
Java- 一度きりの動作差し替えは、匿名クラスで new しながら実装を埋め込めます。
Runnable r = new Runnable() { public void run() { System.out.println("go"); } };
Java- 非 static の内部クラスは「外側のインスタンスに紐づく」ため、
outer.new Inner()と書きます。
class Outer { class Inner {} }
var o = new Outer();
Outer.Inner in = o.new Inner();
Javanew String の落とし穴
文字列は不変で、リテラルのプール共有が効くため、new String("abc") は不要かつ非効率です。通常は "abc" または演算で十分です。
メモリ・ライフサイクルと GC の基礎
参照と到達可能性
オブジェクトは参照が届く限り生きています。参照が切れ、到達不能になると GC が回収します。
User u = new User("U-1", "A");
u = null; // 到達不能 → いつかGCにより回収
Java大量生成や長寿命の参照を持ちすぎるとメモリ圧迫(リーク)を招くため、コレクションからの削除やキャッシュの期限を設計します。
finalize は使わない、try-with-resources を使う
外部リソース(ファイル・ソケット)は「GC任せ」ではなく、明示的にクローズします。
try (var br = java.nio.file.Files.newBufferedReader(java.nio.file.Path.of("data.txt"))) {
System.out.println(br.readLine());
} // ブロック終端で自動クローズ
Java例題で身につける
例 1: 生成と検証の一体化
public final class Money {
private final int amount;
private final String currency;
public Money(int amount, String currency) {
if (amount < 0) throw new IllegalArgumentException("amount>=0");
if (currency == null || currency.isBlank()) throw new IllegalArgumentException("currency");
this.amount = amount;
this.currency = currency;
}
}
var m = new Money(100, "JPY"); // OK
// var bad = new Money(-1, "JPY"); // 例外で拒否
Java例 2: 親子の初期化順序
abstract class Base {
protected final String id;
public Base(String id) { this.id = id; }
}
final class Item extends Base {
private final int price;
public Item(String id, int price) {
super(id); // 先に親
if (price < 0) throw new IllegalArgumentException();
this.price = price;
}
}
Java例 3: レコードと通常クラスの違い(生成の簡素化)
public record Point(int x, int y) {}
var p = new Point(3, 4); // コンストラクタ・equals・hashCode・toString が自動
Java通常クラスで同等の機能を作るより、値オブジェクトは record が簡潔で安全です。
よくあるつまずきと回避
コンストラクタで検証しない
不正な初期状態は後で爆発します。必ず検証して、例外で拒否するか、ファクトリで握る。
乱暴な共有(static 多用)
共有状態が増えるとテスト困難・バグ増。生成は注入(コンストラクタ)に集約し、必要な依存だけ渡す。
無駄な生成とコピー
new String や都度 new SimpleDateFormat などの高コスト生成は避け、必要ならスレッド安全な代替(DateTimeFormatter)や再利用を検討。
仕上げのアドバイス(重要部分のまとめ)
new は「メモリ確保→初期化→コンストラクタ」の順でオブジェクトを生み、以後は参照で扱う。初期状態の検証をコンストラクタ(またはファクトリ)に集め、final で不変を保つ。継承時は super(...) を先に呼び、初期化順序を理解する。大量生成・共有状態・クローズ忘れがトラブルの源——ビルダーや record、try-with-resourcesを使い、読みやすく安全な生成を習慣にしよう。
