インスタンス生成の基本(new)
Javaでオブジェクトを作る最も基本的な方法は new。コンストラクタを呼び、必要な初期化を済ませて“使える状態”のインスタンスを得ます。
class User {
private final String id;
private final String name;
public User(String id, String name) { // コンストラクタ
this.id = id;
this.name = name;
}
}
User u = new User("U001", "Tanaka"); // 直接生成
Java- コンストラクタの役割: 必須フィールドを受け取り、クラスを“整合した初期状態”にする。
- オーバーロード: 引数違いの複数コンストラクタを用意できるが、増えすぎると可読性が落ちる。
- 可視性: 生成を制御したいときはコンストラクタを private にし、代わりにファクトリメソッドを使う。
静的ファクトリメソッド(of/valueOf/create)
コンストラクタの代わりに、クラス側で命名された生成メソッドを提供するスタイル。読みやすさ、柔軟性(キャッシュ・再利用)、返却型の自由度が増します。
class Money {
private final long yen;
private Money(long yen) { this.yen = yen; } // 直接生成を禁止
public static Money of(long yen) { // 命名で意図を明示
if (yen < 0) throw new IllegalArgumentException("負の金額不可");
return new Money(yen);
}
public static Money zero() { return new Money(0); } // 定型生成
}
Money m1 = Money.of(1000);
Money m0 = Money.zero();
Java- 利点:
- 名前で意図を伝える: of, from, parse, empty, cached など。
- 生成戦略を隠蔽: キャッシュ、プール、サブタイプ切り替えを内部で実施。
- 返却型の柔軟性: インターフェース型を返し、実装クラスを隠す。
シンプルファクトリ(専用のファクトリクラス)
生成処理を専用クラスへ切り出す。UIやAPI層が“どの実装を使うか”を意識せずに済みます。
interface Transport { String ship(String item); }
class Truck implements Transport { public String ship(String item) { return "Truck: " + item; } }
class Ship implements Transport { public String ship(String item) { return "Ship: " + item; } }
class TransportFactory {
public static Transport create(String mode) {
switch (mode) {
case "truck": return new Truck();
case "ship": return new Ship();
default: throw new IllegalArgumentException("未知のモード: " + mode);
}
}
}
// 使う側
Transport t = TransportFactory.create("truck");
System.out.println(t.ship("Box"));
Java- 利点: 分岐と生成を一箇所に集約し、呼び出し側の依存を減らす。
- 用途: 設定値や環境に応じた実装切り替え(開発/本番、オンメモリ/DBなど)。
ファクトリメソッドパターン(継承で生成を委譲)
“作り方”をサブクラスに任せる設計。親クラスは処理の流れを持ち、具体的な生成だけを差し替えます。
abstract class ReportGenerator {
// ファクトリメソッド
protected abstract Formatter createFormatter();
public String generate(String data) {
Formatter f = createFormatter(); // 生成点を一箇所に
return f.format(data);
}
}
interface Formatter { String format(String s); }
class CsvFormatter implements Formatter { public String format(String s) { return "CSV:" + s; } }
class JsonFormatter implements Formatter { public String format(String s) { return "{\"msg\":\"" + s + "\"}"; } }
class CsvReport extends ReportGenerator {
protected Formatter createFormatter() { return new CsvFormatter(); }
}
class JsonReport extends ReportGenerator {
protected Formatter createFormatter() { return new JsonFormatter(); }
}
// 使う側
String out = new CsvReport().generate("hello");
Java- 利点: テンプレート(親)+生成の差し替え(子)で拡張容易。
- 適用: 生成以外の処理フローは同じだが、生成する種類だけ変わる場面。
抽象ファクトリ(製品の“族”をまとめて生成)
関連する複数オブジェクトを“セット”として一貫した組み合わせで生成します。
interface UiFactory {
Button createButton();
TextField createTextField();
}
class LightUiFactory implements UiFactory {
public Button createButton() { return new LightButton(); }
public TextField createTextField() { return new LightTextField(); }
}
class DarkUiFactory implements UiFactory {
public Button createButton() { return new DarkButton(); }
public TextField createTextField() { return new DarkTextField(); }
}
// 使う側(テーマを切り替えるだけで族が揃う)
UiFactory f = new DarkUiFactory();
Button btn = f.createButton();
TextField tf = f.createTextField();
Java- 利点: 不整合のない組み合わせを保証(テーマ、プラットフォーム、リージョン等)。
- 用途: 複数コンポーネントの一貫性が重要なUIやプラグイン群。
実務で役立つ生成のテクニックと注意点
- 依存反転(DI)の活用:
- 概念: “どこで作るか”を外部(フレームワーク)に任せる。
- 効果: テスト容易、実装差し替えが設定で可能(Springなど)。
- ビルダーとの使い分け:
- ビルダー: 多数の可任意パラメータ、手順を伴う組み立てに最適。
- ファクトリ: “何を作るか”の選択と生成の隠蔽に最適。
- キャッシュ/プール:
- 静的ファクトリで再利用: 例)Interning、定数オブジェクト、接続プール。
- スレッドセーフ/不変:
- 設計: 生成直後から不変にしておくと安全かつ扱いやすい。
- 例外と整合性:
- 方針: 無効な引数は生成前に検証し、明確な例外で拒否(IllegalArgumentException など)。
- テスト容易性:
- 対処: 生成点をファクトリやDIに寄せ、テストではスタブ/モックを注入。
例題で身につける(解説つき)
例題1: コンストラクタの乱立を静的ファクトリに置き換える
class Customer {
private final String id;
private final String name;
private Customer(String id, String name) { this.id = id; this.name = name; }
public static Customer of(String id, String name) {
if (id == null || name == null) throw new IllegalArgumentException("必須");
return new Customer(id, name);
}
public static Customer guest() { return new Customer("GUEST", "Guest"); }
}
Java- ポイント: 命名で意図を明確化し、検証も一箇所に集約。
例題2: 設定値で実装を切り替えるシンプルファクトリ
class PaymentFactory {
static Payment create(String provider) {
switch (provider) {
case "stripe": return new StripePayment();
case "paypal": return new PaypalPayment();
default: throw new IllegalArgumentException("未知: " + provider);
}
}
}
Java- ポイント: 呼び出し側から new を排除し、依存を減らす。
例題3: ファクトリメソッドでフローを共有
abstract class Exporter {
protected abstract Writer writer();
public void export(String data) {
try (Writer w = writer()) { w.write(data); }
catch (IOException e) { /* ハンドリング */ }
}
}
class FileExporter extends Exporter {
protected Writer writer() { try { return new FileWriter("out.txt"); } catch (IOException e) { throw new RuntimeException(e); } }
}
Java- ポイント: 生成だけ差し替え、処理フローは親で統一。
テンプレート集(すぐ使える雛形)
- 静的ファクトリメソッド
public final class X {
private X(...) { /* validate & init */ }
public static X of(...) { /* validate & return new X */ }
public static X from(Y y) { /* convert & return */ }
public static X cached(K key) { /* cache lookup */ }
}
Java- シンプルファクトリ(分岐一元化)
public final class XFactory {
private XFactory() {}
public static X create(Config cfg) {
if (cfg.useA()) return new XA(cfg);
if (cfg.useB()) return new XB(cfg);
throw new IllegalArgumentException("不正な設定");
}
}
Java- ファクトリメソッド(テンプレート+生成差し替え)
abstract class Base {
protected abstract Part createPart();
public Result run(Input in) {
Part p = createPart();
return p.process(in);
}
}
Java- 抽象ファクトリ(族の一貫性)
interface FamilyFactory { A createA(); B createB(); }
class Variant1 implements FamilyFactory { public A createA(){...} public B createB(){...} }
Javaまとめ
- new: 直接生成。シンプルだが依存が硬直化しやすい。
- 静的ファクトリ: 命名・検証・キャッシュ・返却型の柔軟性を得られる。
- シンプル/ファクトリメソッド/抽象ファクトリ: 生成の責務を分離し、保守性と拡張性を高める。
- 現場のコツ: 生成点を集約、命名で意図を明示、無効値は生成前に拒否、DIで差し替え可能に。
