Javaの文字列結合について学んだ話

初めに

エキサイト株式会社でエンジニアをしている岡崎です。 今回は、Javaで文字列結合の話をしていきます。

文字列結合

文字列結合を行う場合、今まではこのようにやっていました。

    public static void main(String[] args) {
        Integer hour = 12;
        Integer minutes = 24;
        String sample = hour + ":" + minutes;
        
       // 12:24と表示される。
        System.out.println(sample);
    }

文字列結合はバグが起きやすいので、できるだけ安全な形で行うことが大切です。よって、+での連結をできるだけ減らすようにするべきでしょう。

例えば、上記の場合であれば、+ではなく、String.formatを使って以下のように書くことができます。

    public static void main(String[] args) {
        Integer hour = 12;
        Integer minutes = 24;
       
        // 12:24と表示される。
        System.out.println(String.format("%d:%d", hour, minutes));
    }

便利ですね。

配列の文字列結合

ここで気になってくるのは、配列での文字列結合でした。

例えば、 (id=1 OR id=2 OR id=3 OR ・・・OR id=100) といった文字列を作りたかったとします。ただし、idは文字列のListで任意の数だけ持っているとします。

この場合この文字列を作るためには以下の方法が考えられると思います。

Collectorsを使う方法

// id一覧を取得
List<String> idList = getIdList();

String str = "(" + idList.stream()
        .map(e-> String.join("id=", e))
        .collect(Collectors.joining(" OR ")) + ")";

ここではCollectors.joiningを用いて、連結を行なっています。

String.joinを用いて行う

idの文字列の一覧でしかなかったListを、id=~の文字列を配列に変換したあと、String.joinを用いて配列の中身を任意の文字(今回の場合はORになります)で結合する方法です。

String.join(“任意の文字”, 配列);

とすれば、配列が任意の文字で結合されます。

以下に例を記述します。

String[] sample = {"one", "two", "three"};
String str = String.join(",", sample);

// one,two,three と出力
System.out.println(str);

(id=1 OR id=2 OR id=3 OR ・・・OR id=100)という文字列を作るには、

List<String> idList = getIdList();

List<String> list = idList.stream()
        .map(e -> String.join("id=", e))
        .collect(Collectors.toList());
String str =  "(" +  String.join(" OR ", list) + ")";

となります。

なお、+の連結を使っていますが、これをString.joinで使うと見づらくなること、また、()を連結させているだけなので、ここでの+の連結は問題ないこととします。

さて。 見やすさ的には処理が一行であるCollectorsを使った方がいいことは明らかだと思いますが、実行時間はどうなっているのか気になりました。

よって、実際にどうなっているのか測って見ます。

実験

実験のやり方は以下のように行いました。

  1. IDの一覧は100個のランダムな整数とする
  2. それぞれ連結し、10000回実行した時間を測る
  3. 1.2を20回繰り返す

使用したコードは以下です。

public class Main {
    public static void main(String[] args) {
        for (int i=0; i<20; i++) {
            System.out.println("----" + i + "回-----");
            streamJoin();
            stringJoin();
            System.out.println("---------");
        }
    }

    public static List<String> getIdList() {
        List<String> list = new ArrayList<>();
        Random rand = new Random();
        for (int i=0; i<100; i++) {
            list.add(Integer.valueOf(rand.nextInt()).toString());
        }
        return list;
    }

    public static void streamJoin() {
        // id一覧を取得
        List<String> idList = getIdList();

        long start = System.currentTimeMillis();

        for (int i=0; i<10000; i++) {
            String str = "(" + idList.stream()
                    .map(e -> String.join("id=", e))
                    .collect(Collectors.joining(" OR ")) + ")";
        }

        long end = System.currentTimeMillis();
        System.out.println("+:" + (end - start) + "ms");
    }

    public static void stringJoin() {
        // id一覧を取得
        List<String> idList = getIdList();

        long start = System.currentTimeMillis();

        for (int i=0; i<10000; i++) {
            List<String> list = idList.stream()
                    .map(e -> String.join("id=", e))
                    .collect(Collectors.toList());
           String str =  "(" +  String.join(" OR ", list) + ")";
        }

        long end = System.currentTimeMillis();
        System.out.println("+:" + (end - start) + "ms");
    }
}

このコードの実行結果は、以下となりました。

Collectors String
1回 172ms 190ms
2回 93ms 63ms
3回 64ms 57ms
4回 58ms 50ms
5回 51ms 52ms
6回 54ms 54ms
7回 58ms 50ms
8回 53ms 53ms
9回 51ms 49ms
10回 52ms 50ms
11回 51ms 73ms
12回 63ms 51ms
13回 48ms 49ms
14回 47ms 52ms
15回 51ms 51ms
16回 48ms 45ms
17回 47ms 101ms
18回 45ms 47ms
19回 52ms 45ms
20回 48ms 47ms

これらの平均・最大値・最小値は以下です。

平均 60.3 61.45
max 172 190
min 45 45

結論

結果とすると、若干Stringで結合した方が早いですが、かなり微々たるような印象を受けます。読みやすさに関しては好みがあると思いますが、Collectorsを使用した方は1行で終わるので、こちらの方がいいと思っています。なので、自分で実装をするときはCollectorsを使って実装しました。

最後に

今回取り上げた方法も本当に一部ですが、それでも結構考慮する点があると思いました。これからも安全かつ可読性の高い実装を心がけていきたいと思います。