web-dev-qa-db-fra.com

Pourquoi dans Java 8 split supprime-t-il parfois les chaînes vides au début du tableau de résultats?

Avant Java 8 lorsque nous nous sommes séparés sur une chaîne vide comme

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

le mécanisme de scission se scinde aux endroits marqués avec |

|a|b|c|

car un espace vide "" existe avant et après chaque caractère. Donc, comme résultat, il générerait d'abord ce tableau

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

et plus tard, supprimera les chaînes vides de fin (car nous n’avons pas explicitement fourni de valeur négative à limit argument) afin qu’il retourne finalement

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

Dans Java 8 le mécanisme de division semble avoir changé. Maintenant, lorsque nous utilisons

"abc".split("")

nous aurons le tableau ["a", "b", "c"] au lieu de ["", "a", "b", "c"], de sorte qu'il semble que les chaînes vides au début sont également supprimées. Mais cette théorie échoue parce que par exemple

"abc".split("a")

retourne un tableau avec une chaîne vide au début ["", "bc"].

Quelqu'un peut-il expliquer ce qui se passe ici et comment les règles de scission ont changé dans Java 8?

107
Pshemo

Le comportement de String.split (qui appelle Pattern.split) change entre Java 7 et Java 8.

Documentation

Comparaison entre la documentation de Pattern.split in Java 7 et Java 8 , nous observons la clause suivante étant ajoutée:

Lorsqu'il y a une correspondance positive en largeur au début de la séquence d'entrée, une sous-chaîne principale vide est incluse au début du tableau résultant. Une correspondance de largeur zéro au début ne produit toutefois jamais une telle chaîne vide.

La même clause est également ajoutée à String.split in Java 8 , comparé à Java 7 .

Mise en oeuvre de référence

Comparons le code de Pattern.split de l'implémentation de référence dans Java 7 et Java 8.). Le code est extrait de grepcode, pour les versions 7u40-b43 et 8-b132.

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);
}

L'ajout du code suivant dans Java 8 exclut la correspondance de longueur nulle au début de la chaîne d'entrée, ce qui explique le comportement ci-dessus.

            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;
            }

Maintenir la compatibilité

Comportement suivant dans Java 8 et supérieur

Pour que split se comporte de manière cohérente d’une version à l’autre et compatible avec le comportement décrit dans Java 8:

  1. Si votre expression régulière peut correspondre à une chaîne de longueur nulle, ajoutez simplement (?!\A) à la fin de la regex et envelopper la regex originale dans un groupe sans capture (?:...) (si nécessaire).
  2. Si votre expression régulière ne peut pas correspondre à une chaîne de longueur nulle, vous n'avez rien à faire.
  3. Si vous ne savez pas si l'expression rationnelle peut correspondre à une chaîne de longueur nulle ou non, effectuez les deux actions décrites à l'étape 1.

(?!\A) vérifie que la chaîne ne se termine pas au début de la chaîne, ce qui implique que la correspondance est une correspondance vide au début de la chaîne.

Comportement suivant dans Java 7 et versions antérieures

Il n'y a pas de solution générale pour rendre split rétrocompatible avec Java 7 et versions antérieures, à moins de remplacer toute instance de split pour qu'il pointe vers votre propre implémentation personnalisée. .

83
nhahtdh

Cela a été spécifié dans la documentation de split(String regex, limit) .

Quand il y a une correspondance positive en largeur au début de cette chaîne, une sous-chaîne principale vide est incluse au début du tableau résultant. Une correspondance de largeur zéro au début ne produit toutefois jamais une telle chaîne vide.

Dans "abc".split(""), vous obtenez une correspondance de largeur zéro au début, de sorte que la sous-chaîne vide en tête n'est pas incluse dans le tableau résultant.

Cependant, dans votre deuxième extrait lorsque vous vous séparez sur "a" vous avez obtenu une correspondance de largeur positive (1 dans ce cas), de sorte que la sous-chaîne principale vide est incluse comme prévu.

(Code source non pertinent supprimé)

30
Alexis C.

Il y a eu un léger changement dans la documentation pour split() de Java 7 à Java 8.). la déclaration suivante a été ajoutée:

Quand il y a une correspondance positive en largeur au début de cette chaîne, une sous-chaîne principale vide est incluse au début du tableau résultant. Une correspondance de largeur zéro au début mais ne produit jamais une telle sous-chaîne principale vide.

(c'est moi qui souligne)

La division de chaîne vide génère une correspondance de largeur zéro au début. Par conséquent, une chaîne vide n'est pas incluse au début du tableau résultant, conformément à ce qui est spécifié ci-dessus. En revanche, votre deuxième exemple qui se divise sur "a" génère une correspondance positive - width au début de la chaîne, de sorte qu'une chaîne vide est en fait incluse au début du tableau résultant.

14
arshajii