読者です 読者をやめる 読者になる 読者になる

Effective Java

effectivejava java

Effective Javaを読み始めたのでメモなど

項目1 コンストラクタの代わりにstaticファクトリーメソッドを検討する

普通にクラスのインスタンスを生成するならばコンストラクタを使用するけれど、コンストラクタよりもstaticファクトリーメソッドを使ったほうが良いケースがある。

staticファクトリーメソッドの利点

  • staticファクトリーメソッドは、コンストラクタと違って名前を持っているため、仮引数の名前とかコメントだけに頼らずにわかりやすい。
public class Hoge {
  // コンストラクタ
  // "Hoge"クラスなのでコンストラクタの名前はHogeになってしまう
  public Hoge(int i, int j, String str) { }

  // staticファクトリーメソッド
  // 好きなメソッド名を付けることができたり、同じ引数のメソッドだけど違う結果がほしい時にも便利
  public static factoryMethod1(int i, int j, String str) { return XXX; }
  public static factoryMethod2(int i, int j, String str) { return XXX; }
  • staticファクトリーメソッドなら、新たなオブジェクトを生成することが必要ない。

    • immutableなオブジェクトを生成したいときにも良い
    • パフォーマンスが大幅に向上することも
  • コンストラクタと異なり、メソッドの戻り値型の任意のサブタイプのオブジェクトも戻り値に使える。

  • パラメータ化された型のインスタンス生成の面倒さを低減できる。

    • Java 7からはダイアモンド構文があるので、staticファクトリーメソッドじゃなくていいかも。
// "<String, List<String>>"って書くのがめんどい
Map<String, List<String>> map = new HashMap<String, List<String>>();

// Java 7ならダイアモンド構文がある!
Map<String, List<String>> map =  new HashMap<>();

// staticファクトリーメソッドを使えば、上の冗長だった文が簡潔に書ける
public static <K, V> HashMap<K, V> newInstance() {
  return new HashMap<K, V>();
}
Map<String, List<String>> map = HashMap.newInstance();

staticファクトリーメソッドの欠点

  • public or protectedのコンストラクタを持たないクラスのサブクラスを作れない。

  • 他の普通のstaticメソッドと区別がつきづらい。

    • staticファクトリーメソッドの一般的な命名・使用用途は次
      • valueOf / of:
      • getInstance
      • newInstance
      • getType
      • newType

newInstance()ってのはよく見る気がするし、メソッドチェーンぽくインスタンス生成できるのは良さげだと思った。

項目2 数多くのコンストラクタパラメータに直面した時にはビルダーを検討する

  • テレスコーピングコンストラクタ・パターンだと、引数の種類が多すぎると対処するのが大変。
public class Facts {
  private final int calories; // 必須
  private final int fat;      // オプション
  private final int sodium;   // オプション

  // コンストラクタがパターンごとに必要になってしまう
  // また、引数の順序が難しい
  public Facts(int calories) {
    this(calories, 0, 0);
  }
  public Facts(int calories, int fat) {
    this(calories, fat, 0);
  }

  public Facts(int calories, int fat, int sodium) {
    this.calories = calories;
    this.fat = fat;
    this.sodium = sodium;
  }
}

// colaインスタンスの生成
Facts cola1 = new Facts(100, 50, 20);  // {calories, fat, sodium} = {100, 50, 20}
Facts cola2 = new Facts(100, 50);      // {calories, fat, sodium} = {100, 50, 0}
Facts cola3 = new Facts(100);          // {calories, fat, sodium} = {100, 0, 0}
...
  • Java Beansパターンだと、生成の過程で不整合な状態ができてしまうことがある。
public class Facts {
  private int calories = 0;
  private int fat = 0;
  private int sodium = 0;

  // コンストラクタは空で引数もなし
  public Facts() {}

  // Setterで処理
  public void setCalories(int val) { this.calories = val; }
  public void setFat(int val) { this.fat = val; }
  public void setSodium(int val) { this.sodium = val; }
}

// colaインスタンスを生成し、その後Setterで状態を与える
Facts cola = new Facts();
cola.setCalories(100);
cola.setFat(40);
...
  • ビルダーパターンで書くと
public class Facts {
  private final int calories; // 必須
  private final int fat;      // オプション
  private final int sodium;   // オプション

  public static class Builder {
    // 必須
    private final int calories;

    // オプションはデフォルト値で初期化しておく
    private int fat = 0;
    private int sodium = 0;

    // 必須パラメータはコンストラクタで代入
    public Builder(int calories) {
      this.calories = calories;
    }

    // オプションパラメータは好きなだけ
    public Builder fat(int val) {
      this.calories = val; return this;
    }
    public Builder sodium(int val) {
      this.sodium = val; return this;
    }

    public Facts build() {
      return new Facts(this);
    }
  }

  private Facts(Builder builder) {
    this.calories = builder.calories;
    this.fat = builder.fat;
    this.sodium = builder.sodium;
  }
}

// colaインスタンスの生成
Facts cola1 = new Facts.builder(100).fat(50).sodium(20).build();
Facts cola2 = new Facts.builder(100).fat(50).build();

ビルダーパターンはいい感じだけど、記述量が結構増えてしまうので引数が多い状況に使うのが良さそう

テレスコーピングコンストラクタ・パターンやJavaBeansパターンよりはかなり安全。


  • staticファクトリーメソッドは、たまに作っていたけど役割とか利点を理解していたわけではなかったので勉強になった。
  • ビルダーパターンはRubyとかPythonメソッドチェーンみたいに書けて、個人的にはめちゃ良いと思った。