Effective Java 読書メモ 2章
英語版のEffective Java 第3版が出ていたので、日本語版が出るまでの繋ぎに。 もし読み終わっても出る気配が無かったら英語版買ってなんとか解読するしか。
コンストラクタの代わりにstaticファクトリーメソッドを活用する
コンストラクタが複数で、渡す引数によって動きが異なる場合には実装すべきと思う。メソッドの戻り値の型の任意のサブタイプで返せるが、そのサブタイプのコンストラクタはpublicもしくはprotectedでなくてはならない。
pros
- コンストラクタと違い名前を持てること
- メソッドが呼び出される毎に新たなオブジェクトを生成する必要なし
- メソッドの戻り値の型の任意のサブタイプで返せる
- パラメータ化された方のインスタンス生成の面倒さを低減できる
cons
- public/protedctedのコンストラクタを持たないサブタイプは作れない
- 他のstaticメソッドと区別しにくい
// 利用する側 char[] array = new char[] { 'H', 'A', 'T', 'E', 'N', 'A' }; String str1 = new String(array) // コンストラクタの場合 String str2 = String.valueOf(array) // staticメソッドの場合 // 利用される側(jdk8) public String(char value[]) { this.value = Arrays.copyOf(value, value.length); } public static String valueOf(char data[]) { return new String(data); }
数多くのコンストラクタパラメータに直面した時にはビルダーを検討する
個人的には利用側がスマートになって素敵だと思った。パラメータが多くなる場合は、まずクラス設計を見直すべきだと思うが、仕方なくパラメータを増やさざるを得ない場合には良いのでは。
// 利用側のコード public class Main { // arguments are passed using the text field below this editor public static void main(String[] args) { Contact contact = new Contact.Builder("Satoshi", "Matsuda", LocalDateTime.now()).address("東京都千代田区××町3−24").age(27).build(); } } // 利用される側のコード class Contact { private final String firstName; private final String lastName; private final String address; private final int age; private final LocalDateTime contactDate; static class Builder { // 必須パラメータ private final String firstName; private final String lastName; private final LocalDateTime contactDate; // オプションパラメータ private String address = ""; // String.emptyってJavaには無いんだね・・・ private int age = 0; public Builder(String firstName, String lastName, LocalDateTime contactDate){ this.firstName = firstName; this.lastName = lastName; this.contactDate = contactDate; } public Builder address(String val){ address = val; return this; } public Builder age(int val){ age = val; return this; } public Contact build(){ return new Contact(this); // クラス内部ならprivateでも呼べる } } private Contact(Builder builder){ firstName = builder.firstName; lastName = builder.lastName; address = builder.address; age = builder.age; contactDate = builder.contactDate; } }
privateのコンストラクタかenum 型でシングルトン特性を強制する
コンストラクタのアクセス修飾子をprivateにすると初期化の時に1回だけ呼ばれる
singleton実装①
public finalによるシングルトン。単純で分かりやすい。
public class Main { public static void main(String[] args) throws Exception { Singleton obj1 = Singleton.INSTANCE; } } class Singleton { public static final Singleton INSTANCE = new Singleton(); private Singleton(){ /* process */ } }
singleton実装②
staticファクトリーメソッドによるシングルトン。結城先生のデザインパターン本にも載っている方法がこれ。
public class Main { public static void main(String[] args) throws Exception { Singleton obj1 = Singleton.getInstance(); // この時点でSingletonクラスの初期化が行われ、staticフィールドも初期化される Singleton obj2 = Singleton.getInstance(); // Singleton obj3 = Singleton(); エラーが発生する System.out.println(obj1 == obj2); // output : true } } class Singleton { private static final Singleton SINGLETON = new Singleton(); private Singleton(){ /* process */ } public static Singleton getInstance(){ return SINGLETON; } }
singleton実装③
上記の2つの方法だと、シングルトンのクラスをシリアライズするときには、"implements Serializable"を追加するだけではダメとのこと。全てのインスタンスフィールドをtransientと宣言し、readResolveメソッドを実装する必要がある。そうしない場合に、シリアライズされたインスタンスをディシリアライズするたびに、新たなインスタンスが生成されてしまうため、シングルトンを保証できない。transient修飾子は、シリアライズの対象から外すもの。
また、AccessibleObject.setAccesibleメソッドを利用してリフレクションにより、privateのコンストラクタを呼び出すことができてしまうため、シングルトンを保証できない。
Enumを使うとシリアライズするためにアレコレしなくてよくてスマート。
public class Main { public static void main(String[] args) throws Exception { Singleton.INSTANCE.saySingleton(); // singleton!! } } enum Singleton { INSTANCE; public void saySingleton(){ System.out.println("singleton!!"); } }
privateのコンストラクタでインスタンス化不可能を強制する
コンパイラはデフォルトでpublicで引数なしのコンストラクタを提供しているが、明示的にprivateのコンストラクタを用意することでインスタンス化できないようにすることが可能。
不必要なオブジェクトの生成を避ける
staticファクトリーメソッドとコンストラクタの両方を提供している不変クラスの場合は、staticファクトリーメソッドを使用した方が、不必要なオブジェクトの生成を防げる。可変オブジェクトに関しても、メンバのインスタンス生成が一度でいい場合には、static初期化子を使用することを検討すること。
オートボクシング(auto boxing: プリミティブ型→ラッパー型)はパフォーマンスを悪くするので、注意すること。意図した上で変換するのであれば、明示的に行うこと。
廃れたオブジェクト参照を取り除く
クラスが独自にメモリを管理している場合には、例えば下記のようなEffecive JavaにあるようなStackクラスなどに関しては、メモリリークに注意する必要がある。
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(); } return elements[size--]; // 参照値を渡してスタックには残って無いように見えるが、実際はまだ参照値を持っている // 参照値を持っているが、もう参照されることは無い(=廃れた参照) } private void ensureCapacity() { if (elements.length == size) { elements = Arrays.copyOf(elements, 2 * size + 1); } } }
以下のように明示的にnull参照を突っ込むことによって、Garbage Collectionが動いてくれる。
public Object pop () { if (size == 0) { throw new EmptyStackException(); } Object result = elements[size--]; elements[size] = null; // ここ!! return result; }
クラスがメモリを独自管理している場合以外にも、キャッシュやリスナー・コールバックでメモリリークが起きる可能性は高くなる。ヒープの使用量とかデバッグしているときに意識できるといいよね的な感じだろうか。
HashMapを使用している場合には、WeakHashMap(WeakHashMap (Java Platform SE 8))で代替すると、使われることがなくなったキーが自動的に削除されてガベージコレクションの対象となります。
ファイナライザを避ける
C++プログラマがJavaに移ってきたときに誤解しがちなデストラクタ=ファイナライザに対して「そんなことないぞ!!」と。try-finallyを使用して(DBとかファイルはtry-resourcesでしたっけ?)、明示的に終了メソッドを組み込めとのこと。安定してファイナライザは動作をしてくれない。