Java StringTokenizer
Suddividi le stringhe in token in Java con la classe legacy StringTokenizer e scopri quando preferire String.split.
java.util.StringTokenizer è la classe originale del JDK per "spezzare una stringa in parti" — un tokenizer carattere per carattere presente nella piattaforma fin dalla versione 1.0. Il Javadoc stesso sconsiglia il suo utilizzo per il nuovo codice: "StringTokenizer è una classe legacy mantenuta per ragioni di compatibilità, sebbene il suo utilizzo sia sconsigliato nel nuovo codice. Si raccomanda a chiunque cerchi questa funzionalità di usare il metodo split di String o il pacchetto java.util.regex."
Questo è il succo della questione, ed è accurato — ma StringTokenizer appare ancora in basi di codice reali e ha alcune nicchie legittime. Questo capitolo la illustra brevemente, poi indica chiaramente quando è preferibile usare String.split o Scanner.
Il ciclo di base
Un StringTokenizer è un'Enumeration di sottostringhe:
StringTokenizer st = new StringTokenizer("apple banana cherry");
while (st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
// apple
// banana
// cherryIl set di delimitatori predefinito è lo spazio bianco — spazio, tabulazione, newline, ritorno a capo, form feed. I delimitatori adiacenti vengono compressi: " a b " produce esattamente due token.
Delimitatori personalizzati
Il secondo costruttore accetta una stringa i cui caratteri vengono trattati ciascuno come delimitatore — non come una stringa delimitatore:
StringTokenizer st = new StringTokenizer("one,two;three|four", ",;|");
while (st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
// one
// two
// three
// fourQuesta è la particolarità di progettazione che coglie le persone di sorpresa. new StringTokenizer(input, ", ") tratta , e lo spazio ciascuno come delimitatore, non la sequenza a due caratteri ", ". Se hai bisogno di delimitatori a più caratteri, hai superato le capacità di StringTokenizer.
Restituire i delimitatori stessi
Il costruttore a tre argomenti controlla se i delimitatori vengono essi stessi emessi come token:
StringTokenizer st = new StringTokenizer("a+b*c", "+*", true);
while (st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
// a
// +
// b
// *
// cQuesta è l'unica funzionalità che String.split non replica direttamente: bisognerebbe costruirla con un Matcher e un'espressione regolare. Per il parsing di espressioni molto semplici — il tipo di operazione che non giustifica l'uso di un lexer — questo overload ha ancora la sua utilità.
Contare i token in anticipo
countTokens() restituisce il numero di token rimanenti (cioè quelli che verrebbero prodotti da chiamate ripetute a nextToken()). Non li consuma.
StringTokenizer st = new StringTokenizer("a b c d");
int n = st.countTokens(); // 4
while (st.hasMoreTokens()) st.nextToken();
n = st.countTokens(); // 0Questo è occasionalmente utile quando si alloca un array di output della dimensione giusta — anche se con String.split si chiamerebbe semplicemente .split(...).length.
Cambiare il delimitatore a metà flusso
Una funzionalità meno nota: nextToken(String newDelims) reimposta il set di delimitatori per quella chiamata in avanti. Una volta cambiato, il nuovo set persiste per le successive chiamate a hasMoreTokens()/nextToken() finché non lo si cambia nuovamente.
StringTokenizer st = new StringTokenizer("key1=value1; key2=value2; key3=value3");
while (st.hasMoreTokens()) {
String pair = st.nextToken("; ").trim(); // tokens separated by ';' or space
System.out.println(pair);
}Per il parsing occasionale ad hoc questo può essere elegante. Per qualsiasi cosa manutenibile è fonte di confusione — i lettori non si aspettano che un set di delimitatori cambi all'interno di un ciclo.
Perché String.split è generalmente migliore
I motivi per preferire String.split (o Pattern.compile(...).split) nel nuovo codice:
- Veri delimitatori regex. Delimitatori a più caratteri, classi di caratteri, alternanza — tutto naturale.
StringTokenizergestisce solo delimitatori a singolo carattere. - I token vuoti sono visibili.
"a,,b".split(",")restituisce["a", "", "b"].StringTokenizersalta silenziosamente il token vuoto. Per input in formato CSV, "il secondo campo era vuoto" è un'informazione di cui di solito hai bisogno. - Restituisce un array. Facile da indicizzare, facile da convertire in
List, facile da elaborare con stream. - Generalmente più veloce sotto JIT grazie alla cache di
Patternper semplici pattern di split. - Più facile da testare, più facile da leggere. Un ciclo con tokenizer sembra codice del 1996;
parts = csv.split(",")esprime chiaramente l'intento.
Quando potrebbe essere ancora la scelta giusta
Un breve elenco di casi in cui StringTokenizer è ancora difendibile:
- Elaborazione di una stringa molto lunga in streaming dove si vuole consumare token per token senza tenere in memoria un array di tutti i token.
StringTokenizernon alloca il risultato in anticipo;splitsì. - Restituzione dei delimitatori come token per il tokenizer più semplice possibile, senza ricorrere a
Pattern/Matcher. - Manutenzione di codice esistente in cui il resto del file lo usa e la coerenza è la cosa migliore per il prossimo lettore.
Per tutto il resto: usa split.
Un esempio pratico
Il programma seguente tokenizza tre input in tre modi diversi, affiancando le chiamate equivalenti a split, in modo che le differenze comportamentali siano visibili:
Due osservazioni dall'output. Nel caso 1, split riporta la cella vuota tra green e blue ([red, green, , blue]); il tokenizer la comprime ([red, green, blue]). Nel caso 2 entrambi producono gli stessi token, ma per ragioni diverse: il tokenizer spezza mix ad ogni ; e ad ogni spazio indipendentemente ("; " significa "uno qualsiasi di questi caratteri è un delimitatore"), mentre split("; ") corrisponde alla sequenza letterale a due caratteri "; ". Concordano qui solo perché i separatori in mix sono esattamente ; . Cambia l'input in "k1=v1 ;k2=v2" e i due divergono immediatamente. Qualunque comportamento si desideri, il codice dovrebbe essere esplicito — ed è molto più facile con split.
Cosa viene dopo
Finora abbiamo suddiviso le stringhe in parti. Spesso ciò di cui si ha davvero bisogno è trasformare una stringa in un numero, un boolean o un altro primitivo — e viceversa, i primitivi di nuovo in stringhe. Questo round-trip ha il suo insieme di helper e insidie. Continua con Conversioni di stringhe Java.