1. ホーム
  2. java

[解決済み] Java 8のsplitで、結果配列の先頭にある空の文字列が削除されることがあるのはなぜですか?

2022-10-18 21:49:23

質問

Java8以前 のように空文字列で分割した場合

String[] tokens = "abc".split("");

でマークされた箇所を分割します。 |

|a|b|c|

空白のため "" が各文字の前後に存在するからです。その結果、最初にこのような配列が生成されます。

["", "a", "b", "c", ""]

となり、後に は末尾の空文字列を削除します。 (に明示的に負の値を与えなかったため)。 limit 引数に負の値を与えなかったため)、最終的に

["", "a", "b", "c"]


Java 8では の分割メカニズムが変更されたようです。今、私たちが

"abc".split("")

を取得します。 ["a", "b", "c"] の代わりに ["", "a", "b", "c"] .

私が最初に推測したのは、もしかしたら今 をリードする と同じように、空文字列も削除されるようになったのでしょう。 末尾の と同様に削除されます。

しかし、この理論は失敗します。

"abc".split("a")

戻る ["", "bc"] を返すので、先頭の空文字列は削除されません。

ここで何が起こっているのか、誰か説明してください。どのように split のルールはJava 8でどのように変更されましたか?

どのように解決するのですか?

の動作は String.split (これは Pattern.split を呼び出す) は、Java 7 と Java 8 の間で変更されます。

ドキュメンテーション

のドキュメントを比較すると Pattern.split Java 7 Java 8 を追加すると、以下のような句が追加されることが確認できます。

入力シーケンスの先頭に正の幅のマッチがある場合、結果の配列の先頭に空の先頭部分文字列が含まれます。しかし、冒頭のゼロ幅のマッチはそのような空の先頭部分文字列を生成することはありません。

同じ節が String.split ジャバ8 と比較して Java 7 .

リファレンス実装

のコードを比較してみましょう。 Pattern.split のコードを比較してみましょう。コードは、バージョン7u40-b43と8-b132について、grepcodeから取得されています。

Java 7

public String[] split(CharSequence input, int limit) {
    int index = 0;
    boolean matchLimited = limit > 0;
    ArrayList<String> matchList = new ArrayList<>();
    Matcher m = matcher(input);

    // Add segments before each match found
    while(m.find()) {
        if (!matchLimited || matchList.size() < limit - 1) {
            String match = input.subSequence(index, m.start()).toString();
            matchList.add(match);
            index = m.end();
        } else if (matchList.size() == limit - 1) { // last one
            String match = input.subSequence(index,
                                             input.length()).toString();
            matchList.add(match);
            index = m.end();
        }
    }

    // If no match was found, return this
    if (index == 0)
        return new String[] {input.toString()};

    // Add remaining segment
    if (!matchLimited || matchList.size() < limit)
        matchList.add(input.subSequence(index, input.length()).toString());

    // Construct result
    int resultSize = matchList.size();
    if (limit == 0)
        while (resultSize > 0 && matchList.get(resultSize-1).equals(""))
            resultSize--;
    String[] result = new String[resultSize];
    return matchList.subList(0, resultSize).toArray(result);
}

Java 8

public String[] split(CharSequence input, int limit) {
    int index = 0;
    boolean matchLimited = limit > 0;
    ArrayList<String> matchList = new ArrayList<>();
    Matcher m = matcher(input);

    // Add segments before each match found
    while(m.find()) {
        if (!matchLimited || matchList.size() < limit - 1) {
            if (index == 0 && index == m.start() && m.start() == m.end()) {
                // no empty leading substring included for zero-width match
                // at the beginning of the input char sequence.
                continue;
            }
            String match = input.subSequence(index, m.start()).toString();
            matchList.add(match);
            index = m.end();
        } else if (matchList.size() == limit - 1) { // last one
            String match = input.subSequence(index,
                                             input.length()).toString();
            matchList.add(match);
            index = m.end();
        }
    }

    // If no match was found, return this
    if (index == 0)
        return new String[] {input.toString()};

    // Add remaining segment
    if (!matchLimited || matchList.size() < limit)
        matchList.add(input.subSequence(index, input.length()).toString());

    // Construct result
    int resultSize = matchList.size();
    if (limit == 0)
        while (resultSize > 0 && matchList.get(resultSize-1).equals(""))
            resultSize--;
    String[] result = new String[resultSize];
    return matchList.subList(0, resultSize).toArray(result);
}

Java8で以下のコードを追加すると、入力文字列の先頭の長さ0のマッチが除外され、上記の動作が説明されます。

            if (index == 0 && index == m.start() && m.start() == m.end()) {
                // no empty leading substring included for zero-width match
                // at the beginning of the input char sequence.
                continue;
            }

互換性を維持する

Java8以上での動作に準ずる

を作るには split の挙動をバージョン間で一貫させ、Java 8での挙動と互換性を持たせる。

  1. もしあなたの正規表現が が長さゼロの文字列にマッチする場合、単に (?!\A) の後に を追加し、捕捉しないグループで元の正規表現を囲みます。 (?:...) (で囲みます(必要な場合)。
  2. もしあなたの正規表現が はできません。 が長さゼロの文字列にマッチする場合は、何もする必要はありません。
  3. 正規表現が長さ0の文字列にマッチするかどうかわからない場合は、手順1の両方の操作を行います。

(?!\A) は文字列の先頭で終わらないことをチェックします。これは、文字列の先頭で空のマッチであることを意味します。

Java7以前での以下の動作

を作るための一般的な解決策はありません。 split のインスタンスをすべて置き換えるというような、Java 7 以前との後方互換性のある解決策はありません。 split のインスタンスをすべて置き換えて、独自のカスタム実装を指すようにすることです。