Effective Java 第3版を読んだので、本書で紹介されていた全90項目のプラクティスを簡潔にまとめる。
https://www.maruzen-publishing.co.jp/item/?book_no=303408

本記事は第2章「オブジェクトの生成と消滅」の項目について記載する。

第2章 オブジェクトの生成と消滅

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

クラスのインスタンスを与える方法は、publicなコンストラクタを提供するのが一般的な方法である。
別の方法として、publicなstaticファクトリメソッドを提供するという方法があるので提供を検討すべきという内容。

大抵の場合は、staticファクトリメソッドを提供したほうが好ましくなる。

→ staticファクトリメソッドの内部に独自の処理を持たない場合、LombokのstaticName="of"パラメータを使用することでstaticファクトリメソッドの生成が簡単に行える。

メリット

名前(メソッド名)を持つことができる。

特定のシグニチャを持つコンストラクタは1つしか提供できないので、2つ以上のコンストラクタを提供するには引数を異なるものにする必要があった。
対して、staticファクトリメソッドを使うと上記の制約が解消できる上、メソッド名を持つことができるので、返却されるオブジェクトについて理解しやすい表現が可能となる。

呼び出しごとに新たなオブジェクトを生成する必要がない。

イミュータブルなクラスは、あらかじめ生成しておいたインスタンスを返却することで、重複したインスタンスの生成を防ぐことができる。

メソッドの戻り値の型は任意のクラスのインスタンスを返すことができる。

コンストラクタは自身のクラスのインスタンスを返すことしかできないが、staticファクトリメソッドを使うことで任意のクラスのインスタンスを返却することができる。

引数の値によって返却するクラスのインスタンスを変更することができる。

staticファクトリメソッド内で処理を持つことができるので、返却するインスタンスを柔軟に変更することができる。

デメリット

クライアント側がstaticファクトリメソッドの存在を知るのが難しい。

コンストラクタと違いstaticファクトリメソッドは目立ちにくいので存在自体に気づきにくい。
そのため、APIドキュメントを十分に拡充するかメソッドの命名規則を遵守することが大切である。

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

大量のフィールドを持つクラスのインスタンスを生成する場合、コンストラクタやstaticファクトリメソッドを使うと引数の指定が困難になるのと可読性が悪くなる。
そのため、ビルダーパターンを採用したほうが良い。

→ Lombokの@Builderアノテーションをクラスに付与することでBuilderクラスの生成が簡単に行える。

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

シングルトンはインスタンスが1つしか存在しないクラスのこと。
このシングルトン特性を強制するには2通りの方法がとれる。

privateのコンストラクタ

コンストラクタをprivateとして外部から呼ばれないようにする。
その上で、フィールドにstatic finalで定数としてインスタンスを定義し、一度だけコンストラクタを呼ぶようにする。

public class Human {
    private static final Human HOGE = new Human(); // private staic finalでキャッシュ
    private Human() {} // privateコンストラクタ
    
    public static Human ofHoge() { // インスタンスの取得はstaticファクトリメソッドからでのみ可能
        return HOGE;        
    }
}

ただし、この方法にはシリアライズ攻撃やリフレクション攻撃された場合に複数のインスタンスが生成されてしまう恐れがある。

単一要素を持つenum

単一要素にインスタンスを持つenumを定義する方法。
privateのコンストラクタを用意する方法より簡潔で、かつシリアライズ攻撃やリフレクション攻撃を防ぐことができるので、複数のインスタンスの生成をさせなくすることが可能。

public enum Human {
    HOGE;
}

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

ユーティリティクラスのようなインスタンスが不要なクラスにおいてコンストラクタは無意味である。
そのため、privateなコンストラクタを含めることで外部からの呼び出しを不可能にする。

また、万が一呼び出されるケースを考慮してコンストラクタの中で強制的に例外をスローする処理を追加しておくと良い。

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

クラスの柔軟性、再利用性、テストの書きやすさなどの観点より依存する資源はコンストラクタインジェクションすべき。

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

スタイルとパフォーマンスに影響するため余計なオブジェクトの生成は避けるべき。

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

ガベージコレクションを持つJavaであってもメモリ管理は必要である。
意図しないオブジェクトの保持によるメモリリークなどを解決するには参照されなくなった要素に対してnullを設定すべきである。

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

ファイナライザとクリーナーは一般的に以下の理由により必要ではない。

  • ファイナライザやクリーナーが実行されるまでの時間は任意の長さになるため予測ができない。
  • ファイナライザはJava9より言語仕様的に非推奨
  • パフォーマンスが悪い(遅い)
  • ファイナライザ攻撃などによる脆弱性が存在する

また、ファイナライズ処理には、AutoCloseableやtry-with-resourcesなどの別ソリューションがあるのでそちらを使うべき。

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

Javaライブラリの資源をクローズするにはcloseメソッドを呼ぶ必要がある。
closeメソッドの呼び出しを例外の発生に備えて確実なものにするには、try-finally構文を使うのが一般的であった。

しかし、以下の点よりtry-finallyではなくtry-with-resourcesを使うべきである。

  • closeする資源が増えるとtry-finallyの記述が乱雑になる。
  • finally内でcloseメソッドを呼び忘れる場合がある。