Apache Commons Langの StringUtils#split(String, String)
の挙動で地味にハマったのでメモ。
環境
Java 1.8.0_211、Apache Commons Lang 3.9にて確認。
状況
StringUtils#split(String, String)
の第2引数にCRLFを渡すと、CRでもLFでも分割される。
import static org.apache.commons.lang3.StringUtils.*; // AssertJでテスト String CRLF = CR + LF; assertThat(StringUtils.split(1 + CR + 2, CRLF)).containsExactly("1", "2"); assertThat(StringUtils.split(1 + LF + 2, CRLF)).containsExactly("1", "2"); assertThat(StringUtils.split(1 + CRLF + 2, CRLF)).containsExactly("1", "2");
調査と対応
先入観から、 String#split(String)
のNull回避版だろうと思っていたら、仕様は全く違っていた。
https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/StringUtils.html を見るとわかるが、第2引数は「separatorChars」、つまり1文字からなるセパレーターを、複数個文字列として渡しているという挙動。
その他、 String#split
との違いは以下。
String#split
では引数は正規表現として扱われるが、StringUtils#split
では単なる文字列として扱われる- セパレーターが連続した場合、
String#split
では配列に空文字が含まれるが、StringUtils#split
では空文字が含まれない
StringUtils.split(1 + CRLF + 2, CRLF)
とした場合、CRで分割、LFで分割、と2回分割されるが、セパレーターが連続しても空文字にならないため、CRLFで分割しているように見えていた。
文字列全体をセパレーターとして使いたい場合、 StringUtils#splitByWholeSeparator(String, String)
を使う。
こちらも正規表現ではなく、空文字も含まれない。
また、セパレーターが連続した場合に空文字にしたい場合、 splitPreserveAllTokens
や splitByWholeSeparatorPreserveAllTokens
といった、「PreserveAllTokens」付きのメソッドを使用できる。
感想
既存処理の挙動を確認していた時に発見し、なんでだろうと思って調べたら結構驚いた。
この仕様を理解して使っていたなら、書いた人はすごいな~と思っていたが、それ以外の場所で "|||"
のような区切り文字で結合した文字列を、 StringUtils.split(結合した文字列, "|||");
とかやっていたので、たまたまうまく動いていただけっぽい。
使い慣れているクラスでも、たまにはJavadoc見返したりしないとな、と思った。
備考
同じようにハマった人がいたみたい。とても詳しく記載してくれている。