【Effective Java】不必要なオブジェクトの生成を避ける


Effective Javaの独自解釈です。
第3版の項目6について、自分なりにコード書いたりして解釈してみました。

概要

  • どこでオブジェクトが生成されるかを意識する。
  • 用意されているオブジェクトの存在を知る。
  • 何度も同じ値で再利用されるようなオブジェクトは定数化する。
  • ボクシングでオブジェクトが意図せず作成されることに注意する。

不必要にオブジェクトが生成される例と対策

String


  String s = new String("hoge");  

このコードでは、まず”hoge”という文字が入ったオブジェクトが作成され、次にnew String(“hoge”)”hoge”という文字が入った別のオブジェクトが作成される。
要するに中身が同じオブジェクトが二重に生成されるのである。以下のようにすることで、オブジェクト生成を一回で済ませられる。


  String s = "hoge";  

Boolean

Booleanオブジェクトは実は以下のように、Stringオブジェクトを引数にすることで生成することができる。


  Boolean b = new Boolean("true");  

しかしそもそもBooleannull, true, falseの三つの値しかとらない。Booleanクラスにはtruefalseのオブジェクトが定数の形で予め用意されている。

Booleanクラスの一部


    public static final Boolean TRUE = new Boolean(true);
    public static final Boolean FALSE = new Boolean(false);

また、これらの定数を返すvalueOfメソッドも用意されている。


    public static Boolean valueOf(String s) {
        return parseBoolean(s) ? TRUE : FALSE;
    }

ということで、以下のようにvalueOfメソッドを利用してBooleanオブジェクトを宣言すれば、元々Booleanクラスで用意されているオブジェクトを参照し、あらたに生成する必要することがなくなる。


  Boolean b = Boolean.valuOf("true");  

メソッドが呼ばれる度に生成されるオブジェクト

以下の、文字列が正しいローマ数字であるか判定するメソッドを考える。


  static boolean isRomanNumeral(String s) {
    return s.matches("^(?=.)M*(C[MD]|D?C{0,3})"
              + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
  } 

String.matchesは、内部的にPatternオブジェクトを生成しているので、このメソッドが呼ばれる度に新たなPatternオブジェクトが生成されてしまう。
予めPatternオブジェクトを定数として定義しておけば、Patternオブジェクト生成はクラス初期化時の一回で済ませることができる。


  public class RomanNumerals {
    private static final Pattern ROMAN = Pattern.compile(
              "^(?=.)M*(C[MD]|D?C{0,3})"
              + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");

    static boolean isRomanNumeral(String s) {
      return ROMAN.matcher(s).matches();
    } 
  }

String.matchesは内部的にPatternオブジェクトを生成し、そのオブジェクトから.matcher(判定対象文字列).matches()を呼んでいるので、Patternオブジェクト生成部分が丸ごと省略された形となる。

不必要なボクシング

以下のコードを考える。


private static long sum() {
  Long sum = 0L;
  for (long i = 0; i <= Integer.MAX_VALUE; i++)
    sum += 1;

  return sum;
}

sum += 1は内部的にLong sum = sum + (Long)1のように毎回キャストされて演算されているので、毎回Longオブジェクトが生成されてしまう。
そもそもこのメソッドはsumLong型である必要がないため、sumlong型で宣言するだけで大幅に速度改善できる。
これはわかりやすい例だが、わざわざラッパークラスを使用する必要があるかどうかは常に意識すべきである(nullを取る必要性、シチュエーションがありえるかどうかなど)。

注意

メソッドの引数のオブジェクトは、メソッド内部で状態を変更すべきではないという考えがある。今そのオブジェクトがどういう状態になっているかが見えにくく、バグ発生リスクが上がるからである。
よほどパフォーマンスが求めらていない限り、この場合はメソッド内で新たにオブジェクトを生成し、引数のオブジェクトの内容をコピーすることでバグ発生リスクの低減を優先すべきだと考える。