初めに
エキサイト株式会社でエンジニアをしている岡崎です。 今回は、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を使った方がいいことは明らかだと思いますが、実行時間はどうなっているのか気になりました。
よって、実際にどうなっているのか測って見ます。
実験
実験のやり方は以下のように行いました。
- IDの一覧は100個のランダムな整数とする
- それぞれ連結し、10000回実行した時間を測る
- 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を使って実装しました。
最後に
今回取り上げた方法も本当に一部ですが、それでも結構考慮する点があると思いました。これからも安全かつ可読性の高い実装を心がけていきたいと思います。