Effective Javaを読んでまとめてみる
はじめに
秋からの業務でJavaを書くこととなった. Javaを使う上での best practiceを学びたいと思い, おすすめされたEffective Javaを読み始めた. 著者の主張を自分なりに解釈してまとめることとする. 項目数が100近くあるので, いくつかに分ける.
Item 1-5
Item 1: constructorよりもstatic factory methodsを使うこと
static factory methodsの利点は以下である:
- 利点1 : 名前がつけられること
- 利点2 : immutable (呼び出されるときに新しいobjectを作る必要がない)
- 利点3 : 柔軟性(任意のsub typeを返り値として設定可能)
- 利点4 : input parameterであるfunctionによって, 返り値となるobjectを変化させることができる
利点5 : 返り値となるobjectが不要
欠点1 : sub classにできない (public/protected constructorがないclassのため)
- 欠点2 : プログラマにとって, static factory methodsを見つけるのが難しい
Item 2: constructorのparameterが多数のときにはbuilder patternを利用すること
objectを作成する書き方は大きく3種類ある.
Telescoping constructor pattern
個々の引数に応じてconstructorをオーバーロードしまくるパターン.
見ての通りスケールしない.
public class NutritionFacts { private final int servingSize; // (ml) required private final int servings; // (per container) required private final int calories; // (per serving) optional private final int fat; // (g/serving) optional private final int sodium; // (mg/serving) optional private final int carbohydrate; // (g/serving) optional public NutritionFacts(int servingSize, int servings) { this(servingSize, servings, 0); } public NutritionFacts(int servingSize, int servings, int calories) { this(servingSize, servings, calories, 0); } ... }
JavaBeans Pattern
default valueを埋め, setterを利用して値を代入する.
public class NutritionFacts { private final int servingSize = -1; private final int servings = -1; private final int calories = 0; private final int fat = 0; private final int sodium = 0; private final int carbohydrate = 0; public NutritionFacts() { } ....
Builder Pattern
public class NutritionFacts { private final int servingSize; private final int servings; private final int calories; private final int fat; private final int sodium; private final int carbohydrate; public static class Builder() { // Required parameters private final int servingSize; private final int servings; // Optional parameters - initialized to default values private final int calories; private final int fat; private final int sodium; private final int carbohydrate; public Builder(int servingSize, int servings) { this.servingSize = servingSize; this.servings = servings; } 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) { servingSize = builder.servingSize; servings = builder.servings; calories = builder.calories; fat = builder.fat; sodium = builder.sodium; carbohydrate = builder.carbohydrate; } }
以下のように利用できる.
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build()
mandatory parametersはbuilder constructorに引き渡し, optionalなparametersは個別にsetしてbuildする.
このように多数のparametersがある際にはbuilder patternが有効である.
Item 3: singleton propertyにはenumを強いること
singletonの実装には大きく3つの方法がある.
public final fieldで定義
public class Elvis { public static final Elvis INSTANCE = new Elvis(); private Elvis() { ... } public void leaveTheBuilding() { ... } }
- 利点1: singletonのクラスであることが自明
- 利点2: 簡潔
static factory
Date classなどでよく使われている実装方法である.
public class Elvis { private final Elvis INSTANCE = new Elvis(); private Elvis() { ... } public static Elvis getInstance() { return INSTANCE; } public void leaveTheBuilding() { ... } }
- 利点1: 柔軟性がある (例. Factoryはsingletonじゃないobjectを返す, といった変更がAPIを変えずに可能)
- 利点2: genericsを使ったsingleton factoryを利用できる
singletonであることを保証するため, すべてのinstance fieldにtransientを宣言してreadResolve methodを定義する必要がある. そうしなければ, instanceがdeserializeされた際に新しいinstanceが生成されてしまう.
private Object realResolve() { return INSTANCE; }
enum
public enum Elvis { INSTANCE; public void leaveTheBuilding() { ... } }
- 利点1: public final field の方法よりも簡潔
- 利点2: 自動的にserializationできる
- 利点3: 単一instanceを保証できる (serializationやreflectionされた場合においても可能)
よって, singletonにはenumを利用するのが最も良い.
Item 4: private constructorにはnoninstantiabilityを強いること
noninstantiableはconstructorを呼び出すことを許さないことである. noninstantiabilityを得るため, 以下の例では privateなconstructorに AssertionError() を入れている.
public class UtilityClass { // Suppress default constructor for noninstantiability private UtilityClass() { throw new AssertionError(); } }
これにより, constructorが呼ばれないことを保証できる.
Item 5: ハードコード.singletonよりもDIをすること
以下に SpellCheckerクラスを示す.
public class SpellChecker { private static final Lexicon dictionary = ...; private SpellChecker() {} // Noninstantiable public static boolean isValid(String word) { ... } public static List<String> suggestions(String typo) { ... } }
この例では dictionary
をハードコーディングしている.
ハードコーディングには大きく2つの欠点がある: - 欠点1: 柔軟性...言語(日本語, 英語など)によってdictionaryは異なるはずなのに, ハードコーディングでは柔軟に変更できない. - 欠点2: テスタビリティ...SpellCheckerクラスがdictionaryに依存しているため, テストが実施できない.
その他の方法として, SeppCheckerをsingletonとして実装することも可能である. しかしながら, こちらもハードコーディングと同様に柔軟性とテスタビリティの観点から避けるべきである. これらの欠点を克服するために, DIをすべきである.
public class SpellChecker { private static final Lexicon dictionary; // DI private SpellChecker(Lexicon dictionary) { this.dictionary = Objects.requireNonNull(dictionary); } public static boolean isValid(String word) { ... } public static List<String> suggestions(String typo) { ... } }
次回は Item6-10 をまとめる.