Prima di Java 8 quando ci siamo divisi su una stringa vuota come
String[] tokens = "abc".split("");
il meccanismo di divisione si dividerebbe in punti contrassegnati da |
|a|b|c|
perché lo spazio vuoto ""
esiste prima e dopo ogni carattere. Quindi come risultato genererebbe inizialmente questa matrice
["", "a", "b", "c", ""]
e in seguito rimuoverà le stringhe vuote finali (perché non abbiamo fornito un valore negativo per limit
argomento) in modo che alla fine ritorni
["", "a", "b", "c"]
In Java 8 il meccanismo di split sembra essere cambiato. Ora quando usiamo
"abc".split("")
otterremo ["a", "b", "c"]
array invece di ["", "a", "b", "c"]
quindi sembra che anche le stringhe vuote all’inizio vengano rimosse. Ma questa teoria fallisce perché ad esempio
"abc".split("a")
sta restituendo un array con una stringa vuota all’inizio ["", "bc"]
.
Qualcuno può spiegare cosa sta succedendo qui e come le regole di divisione per questo caso sono cambiate in Java 8?
Il comportamento di String.split
(che chiama Pattern.split
) cambia tra Java 7 e Java 8.
Confrontando la documentazione di Pattern.split
in Java 7 e Java 8 , osserviamo la seguente clausola aggiunta:
Quando all’inizio della sequenza di input è presente una corrispondenza di larghezza positiva, all’inizio della matrice risultante viene inclusa una sottostringa principale vuota. Una corrispondenza di larghezza zero all’inizio tuttavia non produce mai una sottostringa così vuota.
La stessa clausola viene anche aggiunta a String.split
in Java 8 , rispetto a Java 7 .
Confrontiamo il codice di Pattern.split
dell’implemetazione di riferimento in Java 7 e Java 8. Il codice viene recuperato da grepcode, per la versione 7u40-b43 e 8-b132.
public String[] split(CharSequence input, int limit) { int index = 0; boolean matchLimited = limit > 0; ArrayList 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); }
public String[] split(CharSequence input, int limit) { int index = 0; boolean matchLimited = limit > 0; ArrayList 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’aggiunta del seguente codice in Java 8 esclude la corrispondenza di lunghezza zero all’inizio della stringa di input, che spiega il comportamento precedente.
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; }
Per fare in modo che la split
si comporti in modo coerente tra le versioni e compatibile con il comportamento in Java 8:
(?!\A)
alla fine della regex e avvolgere la regex originale nel gruppo non di acquisizione (?:...)
(se necessario). (?!\A)
controlla che la stringa non finisca all’inizio della stringa, il che implica che la corrispondenza è una corrispondenza vuota all’inizio della stringa.
Non esiste una soluzione generale per rendere split
backward-compatible con Java 7 e versioni precedenti, a meno di sostituire tutte le istanze di split
per puntare alla propria implementazione personalizzata.
Questo è stato specificato nella documentazione di split(String regex, limit)
.
Quando all’inizio della stringa è presente una corrispondenza di larghezza positiva, all’inizio della matrice risultante viene inclusa una sottostringa principale vuota. Una corrispondenza di larghezza zero all’inizio tuttavia non produce mai una sottostringa così vuota.
In "abc".split("")
hai ottenuto una corrispondenza di larghezza zero all’inizio, quindi la sottostringa vuota principale non è inclusa nell’array risultante.
Tuttavia nel secondo frammento quando ci si divide su "a"
si ottiene una corrispondenza di larghezza positiva (1 in questo caso), quindi la sottostringa principale vuota viene inclusa come previsto.
(Rimosso codice sorgente irrilevante)
C’è stata una leggera modifica nei documenti per split()
da Java 7 a Java 8. In particolare, è stata aggiunta la seguente dichiarazione:
Quando all’inizio della stringa è presente una corrispondenza di larghezza positiva, all’inizio della matrice risultante viene inclusa una sottostringa principale vuota. Una corrispondenza di larghezza zero all’inizio tuttavia non produce mai una sottostringa così vuota.
(sottolineatura mia)
La divisione stringa vuota genera una corrispondenza di larghezza zero all’inizio, quindi una stringa vuota non è inclusa all’inizio dell’array risultante in base a quanto specificato sopra. Al contrario, il secondo esempio che si divide su "a"
genera una corrispondenza di larghezza positiva all’inizio della stringa, quindi una stringa vuota viene di fatto inclusa all’inizio dell’array risultante.