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

avant Java 8 quand nous nous sommes séparés sur une chaîne vide comme

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

split mécanisme permettrait de diviser dans des lieux marqués avec |

|a|b|c|

parce que l'espace vide "" existe avant et après chaque caractère. Ainsi, il générerait d'abord ce tableau

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

et plus tard supprimer les chaînes vides traînantes (parce que nous n'avons pas explicitement fourni de valeur négative à l'argument limit ) donc il va finalement retourner

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

en Java 8 mécanisme de division semble avoir changé. Maintenant, quand nous utilisons

"abc".split("")

nous obtiendrons ["a", "b", "c"] au lieu de ["", "a", "b", "c"] donc il semble que les chaînes vides au début sont également supprimés. Mais cette théorie échoue parce que par exemple

"abc".split("a")

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

est-ce que Quelqu'un peut expliquer ce qui se passe ici et comment les règles de split pour ces cas ont changé en Java 8?

101
demandé sur Community 2014-03-28 20:50:31

3 réponses

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

Documentation

comparant la documentation de Pattern.split dans Java 7 et Java 8 , nous observons que la clause suivante est ajoutée:

Lorsqu'il y a une correspondance positive-width au début de la séquence d'entrée, puis une correspondance vide la sous-couche principale est incluse au début du tableau résultant. Une correspondance zéro-Largeur au début ne produit cependant jamais une telle sous-couche vide.

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

implémentation de Référence

comparons le code de Pattern.split de l'implémentation de référence en Java 7 et Java 8. Le code est récupéré à partir du 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 en Java 8 exclut la correspondance de longueur zéro au début de la chaîne de saisie, 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;
            }

maintien de la compatibilité

suit le comportement en Java 8 et au-dessus

pour faire split se comporte de manière cohérente à travers les versions et compatible avec le comportement en Java 8:

  1. si votre regex peut correspond à la chaîne de longueur zéro, il suffit d'ajouter (?!\A) à la fin du regex et envelopper le regex original dans le groupe de non-capture (?:...) (si nécessaire).
  2. Si votre regex ne peut pas match chaîne de longueur zéro, vous n'avez pas besoin de faire quoi que ce soit.
  3. si vous ne savez pas si le regex peut correspondre à la chaîne de longueur zéro ou non, faites les deux actions à 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 en Java 7 et avant

il n'y a pas solution générale pour rendre split rétro-compatible avec Java 7 et prior, sauf pour remplacer toutes les instances de split pour pointer vers votre propre implémentation personnalisée.

77
répondu nhahtdh 2015-10-30 04:13:54

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

Lorsqu'il y a une correspondance positive au début de cette chaîne ensuite, une sous-couche vide est incluse au début de la tableau résultant. Une correspondance zéro-Largeur au début cependant jamais produit de tels vide leader de la sous-chaîne.

Dans "abc".split("") vous avez obtenu un zéro-largeur match au début ainsi, le substrat vide principal n'est pas inclus dans le tableau résultant.

cependant dans votre second extrait quand vous vous séparez sur "a" vous avez une correspondance de largeur positive (1 dans ce cas), de sorte que le substrat vide est inclus comme prévu.

(suppression du code source non pertinent)

30
répondu Alexis C. 2015-02-19 16:28:19

il y a eu un léger changement dans les docs pour split() de Java 7 à Java 8. Plus précisément, l'énoncé suivant a été ajouté:

Lorsqu'il y a une correspondance positive au début de cette chaîne, alors une sous-chaîne vide est incluse au début du tableau résultant. une correspondance zéro-Largeur au début ne produit cependant jamais une telle sous-couche vide.

(l'emphase est mienne)

la fente de chaîne vide génère une correspondance de largeur zéro au début, de sorte qu'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 un positif -largeur de match au début de la chaîne, alors une chaîne vide est en fait incluse au début de la tableau résultant.

12
répondu arshajii 2014-03-28 16:57:43