Effective Java 3rd [Chapter 2] - オブジェクトの生成と消滅
オブジェクトの生成と消滅
Effective Java 第 3 版の個人的メモ
- 項目 1 コンストラクタの代わりに static ファクトリーメソッドを検討する
- 項目 2 数多くのコンストラクタパラメータに直面した時にはビルダーパターンを検討する。
- 項目 3 private のコンストラクタか enum 型でシングルトン特性を強制する
- 項目 4 private のコンストラクタでインスタンス化不可能を強制する
- 項目 5 資源を直接結び付けるよりも依存性注入を選ぶ
- 項目 6 不必要なオブジェクトの生成を避ける
- 項目 7 廃れたオブジェクト参照を取り除く
- 項目 8 finalizer と cleaner の使用は避けるべし
- 項目 9 try-finally よりも try-with-resources を使うべし
1. 項目 1 コンストラクタの代わりに static ファクトリーメソッドを検討する
1.1. 結論
コンストラクタの代わりに static ファクトリーメソッドを使用すると様々なメリットがある。 そのメリットが必要ない時はコンストラクタを使用すればよい。
1.2. static ファクトリーメソッドとは
static ファクトリーメソッドとは、下記の例のHuman#create()
のようなインスタンスを返す static メソッドである。
Human.java
|
|
1.3. static ファクトリーメソッドのメリット
- 分かりやすいメソッド名を付けられる
- オブジェクト生成のために新しいインスタンスを毎回生成する必要がない。
- コンストラクタの場合、新しいインスタンスを生成する必要がある。
- static ファクトリーメソッドの場合、新しいインスタンスを生成しなくてもよい。(シングルトンパターンにできる。)
- サブクラスのインスタンスでも生成できる(柔軟性が高く、自由に処理を入れることができる。)
- 引数によって、異なるインスタンスを生成できる
- 下記の例のように、引数によって異なるインスタンスを生成することができる。
Human.java
|
|
1.4. static ファクトリーメソッドの使いどころ
- 上記 4 つのメリットを享受したい場合は static ファクトリーメソッドの使用を検討する。
- 例:Logger
- 上記 4 つのメリットがない場合は必要ない。
- 例:DTO
2. 項目 2 数多くのコンストラクタパラメータに直面した時にはビルダーパターンを検討する。
2.1. Telescoping constructor
Telescoping =「順番にあてはめる」=1つ1つ順番にあてはめられるように、受け取れる引数が1つずつ増えていくようにコンストラクタを作成し、それらを順番に呼び出そう。
- 欠点:コンストラクタの引数の何番目に何を設定すれば良いのかがわからなくなる。
2.2. JavaBeans パターン
メンバー変数があり、各メンバー変数のセッターがあるもの。
- 利点:set メソッドで何をセットしているのかはわかりやすい。
- 欠点:インスタンス生成時が、完成した状態ではない。生成後に変更(セット)しないと使えない。
2.3. Builder パターン
値を設定していき、最後にビルドしてインスタンスを生成する方法のこと
- 利点:メンバ変数に final 修飾子をつけておけば、生成後にメンバ変数を変更できないため、インスタンス生成時の状態であることを保証できる。
また下記のような Builder パターンは、fluent API として知られており、
assertThat("hoge").isEqualTo("Hoge");
のように、左から右へ流し読み(一読)できるようになっている。
Builder パターンのクラス例
|
|
Builder パターンの利用方法例
|
|
参考サイト:Builder パターンについて
3. 項目 3 private のコンストラクタか enum 型でシングルトン特性を強制する
3.1. シングルトンなクラス設計の方法について
- private のコンストラクタ
public final
のフィールドによるシングルトン- 欠点:リフレクションにより、
private
のコンストラクタを呼び出せるので、シングルトンが破綻すること
- 欠点:リフレクションにより、
Elvis.java
|
|
static
ファクトリーメソッドによるシングルトン- 利点 1:final を利用しているので、リフレクションを使ったとしても防止できる。
- 利点 2:API を変更せずに、シングルトンか否かを変更できる。
Singleton.java
|
|
参考サイト:正しいシングルトンクラスの実装の仕方
3.2. ENUM 型を使ったシングルトン
Elvis.java
|
|
- Java の世界でインスタンスが 1 つであることを保証
- 並列処理の場合でも必ず1つ
- シリアライズについて考慮しなくて良いから推奨
4. 項目 4 private のコンストラクタでインスタンス化不可能を強制する
4.1. 結論
ユーティリティクラスは private のコンストラクタでインスタンス化不可能を強制するとよい。
4.2. ユーティリティクラスとは
ユーティリティクラスとは、static メソッドをまとめたクラス
- 例:java.util.Math や java.util.Arrays)
実装例
|
|
- 利点
- インスタンス化する意味がないクラスをインスタンス化不可能にできる点。
- 副次的に、サブクラスを作成することを防げる点。
- 防げる理由は、すべてのクラスはスーパークラスのコンストラクタを呼び出さなければならないが、呼び出せるアクセス可能なコンストラクタがないため。
public final
とすることで、継承をさせる隙すら与えない。
4.3. インスタンス化不可能にする理由
- インスタンス化する必要がないのであれば、使う人に考えさせないようにインスタンス化不可能にするべき
- java.util.Math のようなユーティリティクラスは 1996 年からあり、メモリが少ない時代であったという時代背景もありそう。
5. 項目 5 資源を直接結び付けるよりも依存性注入を選ぶ
5.1. 結論
DI(Dependency Injection)を使うとテストがしやすくなる。
5.2. 資源を直接結び付ける例
下記サンプルコードは、DB が Mysql であることが前提となっていて、Mysql が起動していないとテストできない。 (昔はそのようにテストしていた。)
DaoBLogic.java
|
|
5.3. 依存性注入の例
下記サンプルコードは、DB が何であるかは、依存性注入時に決めることができる。 つまり、Mock の DB を注入してもテストできるため、柔軟である。
DaoBLogic.java
|
|
インジェクションには以下の3つの方法がある。
- コンストラクタインジェクション
- セッターインジェクション
- フィールドインジェクション(Spring なら@Autowired)
6. 項目 6 不必要なオブジェクトの生成を避ける
6.1. 結論
一度生成されたら変更されないオブジェクトは再利用したり、自動ボクシングに注意したりすることで、パフォーマンスを落とさないようにしよう。
6.2. 生成に時間がかかる処理について
java.util.Calendar
クラス- デフォルトロケール取得等の処理が必要であるため。
- 例外クラス
- stacktrace を収集するため。
- DB コネクションクラス
- 接続コスト(TCP コネクション確立~ DB の認証)のため。
- 正規表現のパース処理
- 正規表現と検査文字列の長さに対して、O(n^2)のオーダーがかかるため。
自動ボクシングにも時間がかかる。 インスタンス生成が走るため。int -> Integer など。
7. 項目 7 廃れたオブジェクト参照を取り除く
7.1. 参照について
- 直接参照
- 直接値を見ている参照
- 関節参照
- アドレスから値を取得している参照
7.2. 強参照と弱参照
- 強参照
- 変数によりアドレスが参照されている状態
- 弱参照, (ソフト参照、ファントム参照)
- アドレスがどの変数からも参照されていない状態
強参照が無くなったオブジェクトは、GC の対象となる。
7.3. 気をつけること
MAP などは、強参照で保持し続けるため、個数の上限を決めて、LRU(Least Recently Used)や FIFO などのアルゴリズムで対処しましょう。
以下のライブラリを使うという方法もある。
- Ehcache
- Java のキャッシュライブラリの代表で高機能。
- Guava Cache
- Google のライブラリである Guava に、キャッシュ用のパッケージも含まれており、基本的なことは可能。
- Apache DirectMemory
- JavaVM ヒープ外のメモリをキャッシュに使用。
- 利点:ヒープ内のキャッシュと比べて、GC の影響を受けない。
- 欠点:シリアライズ/デシリアライズのコストがかかる。
参考サイト:Java で使えるオープンソース・キャッシュライブラリ
8. 項目 8 finalizer と cleaner の使用は避けるべし
8.1. Finalizer/Cleaner
- finalizer : オブジェクトが GC で回収されるときに、
java.lang.Object#finalize
が実行される。- 実行する人:Finalizer スレッド
GCスレッド -(put)-> (Finzelizerキュー) -(get)-> Finalizerスレッド
- finalize でやりたいこと
- リソースの解放(DB コネクションのクローズ、ファイルの接続)
- 問題 1
- ファイナライザいつまでたっても終わらない問題
- tomcat スレッド数が 10-20 に対して、GC を行う Finalizer スレッドが 1 スレッドなので、1 対多となり、GC が間に合わなくなる時がある。
参考:Finalizer スレッドは 1 つで、http-nio
スレッドは複数ありますね。
|
|
- 問題 2
- ファイナライザ内で例外おきたらどうなるの問題
- Finalizer スレッドが、A -> B の順でクローズすべきところを、B -> A の順でしてしまい例外など。
- なお、Finalizer スレッド内の順序制御は出来ない。
8.2. 結論
try-finally の finally 句内で正しい順序でクローズしましょう。
Spring にも終了時の処理を挟む API があるので、使いましょう。
9. 項目 9 try-finally よりも try-with-resources を使うべし
9.1. 結論
基本的には、close
しなければならないリソースを扱う場合、try-finally
ではなく、try-with-resources
を使いましょう。
9.2. try-finally の場合
TryFinally.java
のように書く必要があり、以下の欠点がある。
- finally 句で、非 null チェックする必要がある。
- finally 句の中で例外が発生したらスローすることになる。
TryFinally.java
|
|
豆知識
try 句内で例外が発生し、finally 句でも例外が発生すると、finally 句の例外だけがエラーメッセージとして出る。
以下のように、finally 句で出た例外を揉み消せば、try 句内で出た例外がスローされるよ。
|
|
9.3. try-with-resources の場合
TryWithResources.java
のように書きましょう。以下のような利点がある。
finally
句で close しなくても、自動的で close をしてくれる。- close 処理中に、例外が発生しても try 文の中の例外がスローされる。
TryWithResources.java
|
|
ただし、try-with-resources でリソース解放されないパターンで紹介されているように、「リソース解放の対象クラスをネストさせてインスタンス生成した場合、コンストラクタで例外が発生するとリソース解放されません。」ので、この場合は、[ 個別にフィールドを宣言し、それぞれインスタンス生成]して対処するようにしましょう。