何かで区切るする表現、splitter - Javaとかで学ぶ、すぐに使いたくなる洗練されたプログラムイディオム集処理結果を出力するプログラムでは、次のようにコンマやスペースなどでデータを区切りながら出力することが意外と多い。そこで今回は、こうした処理をシンプルに書く方法を考える。
この処理の肝は、次のように先頭もしくは末尾につく区切り文字(デリミタ)を省くことだ。
次の実装の最大の問題は、末尾の区切り文字の出力を抑止しようとしていることだ。予め長さがわかっている場合にはありえなくないアプローチだが、ネットワークからデータを読み込みながら出力する場合など、長さを予め知ることができなかったり、長さを知るためにコストがかかる(リストをたどらなければならないなど)場合は使えない。 1static String joinかなりいまいち(String separator, List<String> list) { 2 StringBuffer result = new StringBuffer(); // シングルスレッドでもSringBufferを使うのは過去のこと 3 for (int i = 0; i < list.size(); i++) { 4 String e = list.get(i); 5 result.append(e); 6 if (i < list.size() - 1) { // 長さが予めわかっていないとだめというのはIteratorでは困る 7 result.append(separator); 8 } 9 } 10 return result.toString(); 11} 次のコードは幾分改善されている。しかし、まだif文が余計だ。 1static String joinいまいち(String separator, Iterable<String> list) { 2 StringBuilder result = new StringBuilder(); 3 boolean first = true; 4 for (String e : list) { 5 if (!first) { 6 result.append(separator); 7 } 8 result.append(e); 9 first = false; 10 } 11 return result.toString(); 12} プログラムをシンプルにするには、if文を取り除くこことが一番いい。プログラムは、逐次(順番に実行する)、反復(ループ)、選択(if,switch文)という制御構造で記述するが、このうち選択がなければ、テストはどんなに楽だろう、デバッグはどんなに楽だろう。というわけで、if文を取り除いたバージョンがこれだ。 1static String join正解(String separator, Iterable<?> list) { 2 StringBuilder result = new StringBuilder(); 3 String delimiter = ""; 4 for (Object e : list) { 5 result.append(delimiter); 6 result.append(e.toString()); 7 delimiter = separator; 8 } 9 return result.toString(); 10} 最初の区切りを出力しないのではなく、最初だけ空文字列を区切りとして出力することで、if文を不要にしている。文字列の連結の場合、空文字列を連結することは何もしないことと同じだ。何もしないことと同等な操作をNOP(NO Operation)というが(もともとアセンブリ言語の命令)、この場合空文字列はNOPにあたる。NOPをうまく使うことで、特別扱いしなければならないパターンを取り除きプログラムをシンプルにすることができる場合がある。 |