【Java】コンストラクタの代わりにStaticFactoryMethodを使う

オブジェクト指向の言語では、コンストラクタという概念がある。
一般的に以下のような形で記述する。

package staticFactoryMethod;

public class Task {
    private int num;
    
    public Task(int num) {
        this.num = num;
    }
}

ただ、コンストラクタは以下の仕様を実現することが出来ない。

  • 生成されるインスタンスの数を1つに制限する
  • 引数の値に応じてサブクラスを戻り値にする

そこで検討すべきであるのが、StaticFactoryMethodである。

インスタンスの生成を制限

package staticFactoryMethod;

public class Singleton {
    private static Singleton singleton = new Singleton();
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        return singleton;
    }
}

こんな感じに記述すれば、
SingletonクラスのインスタンスはgetInstanceメソッドを通じてしか取得されないし、
返されるインスタンスはいつも同じである。(Singletonパターンに該当)

package staticFactoryMethod;

public class StartUp {

    public static void main(String[] args) {
        // エラーになる
        // Singleton singleton = new Singleton();
        
        // いつでも同じインスタンスを取得可能
        Singleton singleton = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        
        // 実行結果: true
        System.out.println(singleton==singleton2);
    }

}

サブクラスを戻り値にして実装を隠す

また、引数の値に応じて異なるクラスを返すことが出来る。
使用者は自分がSmallTaskを手に入れたのかBigTaskを手に入れたのかを
気にせずに使うことが出来る。(実装を隠せる)

package staticFactoryMethod;

public abstract class Task {
    public static Task newInstance(int num) {
        return num>10 ? new BigTask(num) : new SmallTask(num);
    }
}
package staticFactoryMethod;

public class StartUp {

    public static void main(String[] args) {
        Task task = Task.newInstance(64);
        
        // 実行結果: class staticFactoryMethod.BigTask
        System.out.println(task.getClass());
    }

}

【Java】String, StringBuffer, StringBuilderの使い分け

Java初心者が見かける3つの文字列系のクラスについて使い分けや特徴を書いていく。
まず結論から書くと、

String

使いどころ:
  文字列を頻繁に変化(再代入、+演算子での連結など)させなくてよい場合
メリット:
  メモリの確保が最小限
デメリット:
  操作に対するパフォーマンスが極めて悪い(処理が遅い

StringBuffer

使いどころ:
  マルチスレッド環境下で、同じ文字列にアクセスして変更や結合などを頻繁に行う場合
メリット:
  Stringより文字列の操作が速い
デメリット:
  可変長であるため、初めに余分にメモリを確保する
  StringBuilderよりパフォーマンスが落ちる

StringBuilder

使いどころ:
  シングルスレッド環境下で、文字列の変更や結合などを頻繁に行う場合
メリット:
  Stringより文字列の操作が速い(最速!)
デメリット:
  可変長であるため、初めに余分にメモリを確保する
  マルチスレッド環境下では実行結果が保証されない

ということで3つを使い分けるといいらしい。
とはいえスレッドセーフで文字列を変更しなければならない場合なんてそうないらしく、
Stringか操作が一番早いStringBuilderを使うのが多いとのこと。


各クラスの詳細

ここからは、各クラスについてもう少し深く掘り下げてみる。

String

Stringは固定長でimmutable(不変)な文字列を扱うクラスである。
つまり、本来は文字列を再代入したり連結できないのだが、暗黙的に新しいインスタンスを生成1している。 新しいインスタンスの生成(new)は重い処理であるためパフォーマンスが低くなる。

package stringClasses;

public class StringTest {

    public static void main(String[] args) {
        String str = "俺はStringだ";
        
        // 再代入すると内部では、新しくインスタンスが生成される
        // str = new String("俺はStringなのか");
        str = "俺はStringなのか";
        
        // 結合も内部では、結合した後の文字列で
        // 新しくインスタンスを生成している
        // str = new String("俺はStringなのか?");
        str += "?";
    }

}

100回の連結にかかる時間も計測しておこう。

package stringClasses;

public class StringTime {

    public static void main(String[] args) {
        String str=new String();
        
        // 結合前の時刻を記録
        long startTime = System.nanoTime();
        
        for(int i = 0; i < 100; i++) {
            str += "!";
        }
        
        // 結合後の時刻を記録
        long endTime = System.nanoTime();
        
        // かかった時間を表示
        System.out.println(endTime - startTime);
    }

}
出力結果(ナノ秒): 113000

StringBufferとStringBuilder

StringBufferとStringBuilderの違いは、スレッドセーフか否かである。
実際に実装を確認してみても、大きな違いはsynchronized修飾子2があるかどうか。
最後に処理速度を比較しておこう。
まずはStringBufferから。

package stringClasses;

public class StringBufferTime {

    public static void main(String[] args) {
        StringBuffer strbf = new StringBuffer();

        // 結合前の時刻を記録
        long startTime = System.nanoTime();

        for (int i = 0; i < 100; i++) {
            strbf.append("!");
        }

        // 結合後の時刻を記録
        long endTime = System.nanoTime();

        // かかった時間を表示
        System.out.println(endTime - startTime);
    }

}
出力結果(ナノ秒): 31300

本当に記述方法が合ってるのか不安になるレベルで変わる。

次にStringBuilder。

package stringClasses;

public class StringBuilderTime {

    public static void main(String[] args) {
        StringBuilder strbl = new StringBuilder();

        // 結合前の時刻を記録
        long startTime = System.nanoTime();

        for (int i = 0; i < 100; i++) {
            strbl.append("!");
        }

        // 結合後の時刻を記録
        long endTime = System.nanoTime();

        // かかった時間を表示
        System.out.println(endTime - startTime);
    }

}
出力結果(ナノ秒): 29100

使い分け超大事だなと実感した。

【Java】+演算子、StringBuilder、StringBufferの違い(実際に測ってみた。)、文字列系の型 | プログラミングマガジン
【Java】StringBuilderとStringBufferの違いをスレッドセーフの観点で検証してみた - カタカタブログ


  1. Stringは全く同じ文字列であれば再利用される(新しいインスタンスは生成されない)
  2. 複数のスレッドが同時にメソッドを実行できないようにする