Edited at

Effective Java 第3版 第2章オブジェクトの生成と消滅

Java中級者以上の必須本である、Effective Java 第3版に Kindle版が出たので、まとめる。

「第1章 はじめに」は、主に用語の説明なので飛ばす。

前:なし

次:Effective Java 第3版 第3章全てのオブジェクトに共通のメソッド


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


  • staticファクトリメソッドとは、Boolean.valueOf(boolean b)みたいなやつ。


[Good]staticファクトリメソッド例

public static Boolean valueOf(boolean b) {

return b ? Boolean.True : Boolean.False
}


  • staticファクトリメソッドのメリット


    • コンストラクタとは異なり、名前を持つ。


      • コンストラクタは名前が無い為、パラメータで機能を判断するのは難しい。



    • コンストラクタとは異なり、その呼び出しごとに新たなオブジェクトを生成する必要がない。


      • 不必要に重複したオブジェクトの生成を避けられる。



    • コンストラクタとは異なり、メソッドの戻り値型の任意のサブタイプのオブジェクトを返せる。

    • 返されるオブジェクトのクラスは、入力パラメータの値に応じて呼び出しごとに変えられる。

    • 返されるオブジェクトのクラスは、そのstaticファクトリメソッドを含むクラスが書かれた時点で存在する必要がない。



  • staticファクトリメソッドの制約


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


      • 例えば、コレクションフレームワーク内のユーティリティ実装クラスのどれかのサブクラスを作ることはできない。


        • 継承ではなく、コンポジションを使うべき。





    • プログラマがstaticファクトリメソッドを見つけるのが難しい。



  • staticファクトリメソッドの命名規則


    • from

    • of

    • valueOf

    • instance or getInstance

    • create or newInstance

    • getType

    • newType

    • type




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


[Good]ビルダークラス例

public class NutritionFacts {

private final int savingSize;
private final int savings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;

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

// オプション デフォルト値
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;

public Builder(int savingSize, int savings) {
this.savingSize = savingSize;
this.savings = savings;
}

public Builder calories(int val) {
calories = val;
return this;
}

public Builder fat(int val) {
fat = val;
return this;
}

public Builder sodium(int val) {
sodium = val;
return this;
}

public Builder carbohydrate(int val) {
carbohydrate = val;
return this;
}

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

}

private NutritionFacts(Builder builder) {
savingSize = builder.savingSize;
savings = builder.savings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
}



lombokの@Builder使用例


lombokを使った例

import lombok.Builder;

import lombok.NonNull;

@Builder
public class LombokNutritionFacts {

// 必須
@NonNull
private final Integer savingSize;
@NonNull
private final Integer savings;

// デフォルト値の設定
@Builder.Default
private final Integer calories = 0;
@Builder.Default
private final Integer fat = 0;
@Builder.Default
private final Integer sodium = 0;
@Builder.Default
private final Integer carbohydrate = 0;
}



項目3 privateのコンストラクタかenum型でシングルトン特性を強制する


[Good]enum型を用いたシングルトンクラス

// enum型を用いたシングルトンクラス

public enum Elvis {
INSTANCE;

public void leaveTheBilding() {
System.out.println("Hello Elvis!");
}
}

// 呼び出し例
public class Main {
public static void main(String[] args){
Elvis elvis = Elvis.INSTANCE;
elvis.leaveTheBilding();
}
}



項目4 privateのコンストラクタでインスタンス化不可能を強制する


[Good]

// インスタンス化できないユーティリティクラス

public class UtilityClass {
// インスタンス化できないように、privateでコンストラクタを作成する
private UtilityClass() {
throw new AssertionError(); // クラス内で呼ばれたらスローする
}
// 以下省略
}


項目5 資源を直接結び付けるよりも依存性注入を選ぶ


[Bad]

// 静的なユーティリティの不適切な使用。柔軟性に欠けてテストできない。

public class SpellCheckerStatic {
private static final Lexicon dictionary = new MyDictionary();

private SpellCheckerStatic() {
}
public static boolean isValid(String word) {
// 省略
}
public static List<String> suggestions(String type) {
// 省略
}
}



[Good]

// 依存性注入は柔軟でありテストできる

public class SpellChecker {
private final Lexicon dictionary;

public SpellChecker(Lexicon dictionary) {
this.dictionary = Objects.requireNonNull(dictionary);
}
public boolean isValid(String word) {
// 省略
}
public List<String> suggestions(String type) {
// 省略
}
}



項目6 不必要なオブジェクトの生成を避ける

// これは不必要なオブジェクトの生成をするため、

String s = new String("bikini");
// こうするべきである
String s = "bikini";


  • コンストラクタよりもstaticファクトリメソッドを使うべきである。

  • ボクシングされた基本データ型よりも基本データ型を選び、意図しない自動ボクシングに注意すること。

public class RomanNumerals {

private static final Pattern ROMAN = Pattern.compile(
"^(?=.)M*(C[MD]|D?C{0,3})" +
"(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");

// 【良い例】 オブジェクトを再利用した改善版
static boolean isRomanNumerial(String s) {
return ROMAN.matcher(s).matches();
}

// 【良くない例】 繰り返し使うとパフォーマンスが悪い
static boolean isRomanNumerialBad(String s) {
return s.matches("^(?=.)M*(C[MD]|D?C{0,3})" +
"(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
}
}


項目7 使われなくなったオブジェクトの参照を取り除く


  • 使われなくなった参照にはnullを設定する。

  • オブジェクト参照にnullを設定することは、普通というよりむしろ例外であるべき。

  • 使われなくなった参照を排除する最善の方法は、その参照が含まれていた変数をスコープの外に出すことである。

public class Stack {

private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // ■ 使われなくなった参照を取り除く ■
return result;
}
// 大きくする必要があれば倍にする。最低でも1つの容量を確保する。
private void ensureCapacity() {
if (elements.length == size) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}


項目8 ファイナライザとクリーナーを避ける


  • ファイナライザは、予測不可能で大抵は危険で一般的には必要がない。

  • Java9からのファイナライザの代替は、クリーナーで、ファイナライザよりも危険ではないが、それでも予想不可能で遅く、一般的には必要がない。

  • ファイナライザやクリーナーの代わりに、AutoClosableを実装させ、必要がなくなった時点でcloseメソッドを呼び出すことを要求する。大抵はcloseに、try-with-resourceを使う。


項目9 try-finallyよりもtry-with-resourcesを選ぶ

その名の通り。