読者です 読者をやめる 読者になる 読者になる

Jの衝動書き日記

さらりーまんSEの日記でございます。

Effective Javaの読書メモ 第2章 オブジェクトの生成と消滅 その1

 Effective Javaを2回ほど読んだので、その時の読書メモを公開する。

 本の内容はどちらかというと中級者向きなのでJavaを書いたこともない人にはオススメしにくい。だけど内容は基礎的なことが多いのでそんなに難しくはない。書くことにある程度は慣れてきたけど、もう少し先に進みたいという人に是非オススメしたい本だ。同様の本としてはリーダブルコードとリファクタリングがある。

 本の内容はいくつかのエッセンスを凝縮したものなので、読書メモも何回かにわけて公開することにする。今回は第2章で気になったものだ。

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

 オブジェクトを生成するときにはコンストラクタを使うが、次のような欠点がある。

  1. オブジェクトの作成にパターンがある場合、どのようなオブジェクトが作成されるのかがわかりにくい
  2. 特定のシグネチャー(引数)を持つコンストラクタは一つしか定義出来ない

 1.に関してだが、コンストラクタ複数ある場合、どのコンストラクタを呼ぶべきなのか? 直感的にわからない。作成されるオブジェクトに違いはあるのか? それともないのか? javadocをみればわかることもあるが、名前からはわかりにくい。そのため誤用の元にもなる。

 2.に関してだが、例えば以下のような定義は出来ない。

public class Hoge {
  private final int a;
  private final int b;
  private final int c;

  public Hoge(int argA, int argB) {
    a = argA;
    b = argB;
    c  = 0;  // デフォルト値
   }
   // 引数の型が上と同じであるため定義出来ない
   public Hoge(int argB, int argC) { 
     a = -1;  // デフォルト値
     b = argB;
     c  = argC;
   }
}

 上記の例では、状況に応じてaかcをデフォルト値で設定しようとしているが、それは出来ない。引数の数か引数の型を変える必要がある。取る引数の型が異なる場合は、引数の並びを変えることで定義できるが、いずれにしてもこれらも誤用のもとである。

 

 この場合の解決策は「staticファクトリーメソッドを作成する」ということである。呼んだらそのオブジェクトを返すstaticメソッドのことだ。

 この利点は以下の通りだ。

  1. コンストラクタと異なり名前を持つ 
  2. メソッドを呼び出されるごとに新たなオブジェクトを生成する必要がない
  3. メソッドの戻り値型の任意のサブクラスのオブジェクトでも返せる
  4. パラメータ化された型のインスタンス生成の面倒さを軽減する

 1.に関しては、用途に応じてわかりやすい表現の名前を付けれるということだ。作成するインスタンスの種類に応じてメソッドを定義すれば誤用も避けられる。

public class Person {
  boolean sexType ; // true:male false:femaleと定義
  public Person(boolean sexType) {
     this.sexType = sexType;
  }
}
new Person(true);

この場合、以下の方がいい

public class Person {
  boolean sexType ; // true:male false:femaleと定義
  protected Person(boolean sexType) {
     this.sexType = sexType;
  }
  public static Person newInstanseForMale()   { return new Person(true);  }
  public static Person newInstanseForFeMale() { return new Person(false); }
}

 maleインスタンスはnew Person(true) ではなく Person.newInstanseForMale();で得られる。メソッド名でmaleかfemaleかわかるので誤用が少ない。

 ところでこれはクラス設計としてはもちろん駄目な例である。sexTypeで分けるぐらいなら、子クラスのMale,Famaleクラスを作った方が良いのだが、それはさておく。

 

 2.に関しては、予めいくつかの不変オブジェクトを作成してstaticで保持しておけばわざわざnewする必要もないということだ。singleton実装とかでもよくやるが、何も一つにとどめておく必要もない。

 public static Boolean valueOf(boolean b) {
     return b ? Boolean.TRUE : Boolean.FALSE;
 }

 または、上記であげた例の場合は以下の通り。

 public class Person {
    private static final Person MALE = new Person(true);
    private static final Person FAMALE = new Person(false);
    
    public static Person getInstanseForMale() { return MALE; }
    public static Person getInstanseForFaMale() { return FAMALE; }
    …以下略
 }

 作成されるオブジェクトのパターンが限られている場合はimmutable(不変)のオブジェクトにしてしまった方が色々楽だ。

 

 3.に関しては、Factoryパターンとかでお馴染みだと思う。LoggerとかJDBCとかで普通に使ってる。パラメータに応じて返すクラスを変えれるし、クラスはpublicである必要もない。

 

 4.に関しては型パラメータを取るようなクラスの場合(例えばList<E>など)、staticファクトリーメソッドを用意しておけば型推定が働くためわざわざ2回も宣言する必要がない。

 

ただ、以下のような欠点もある。

  1.  publicあるいはprotectedのコンストラクタを持たないクラスのサブクラスを作れない
  2. 他のstaticメソッドとの区別が容易ではない

 ただ、1に関してはサブクラスを作らないでコンポジション――利用対象のクラスを要素として持たせる――を使えばよいので問題はない。本では継承よりもコンポジションによるクラス拡張を薦めている。

 また、2に関しては一般的な名前を使えば良いということでもある。以下がその例。

  • valueOf
  • of
  • getInstance
  • newInstance
  • getType
  • newType

 getInstanceとgetType、newInstanceとnewTypeの違いはこのメソッドが対象となるクラスにあるかないか。getとnewの違いは、getの場合はインスタンスは同一の場合もあれば異なる場合もあるが、newの場合は常に異なるインスタンスが返るという違いがある。

参考文献

Effective Java 第2版 (The Java Series)

Effective Java 第2版 (The Java Series)