W3docs

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
// cherry

Il 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
// four

Questa è 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
// *
// c

Questa è 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();            // 0

Questo è 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. StringTokenizer gestisce solo delimitatori a singolo carattere.
  • I token vuoti sono visibili. "a,,b".split(",") restituisce ["a", "", "b"]. StringTokenizer salta 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 Pattern per 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. StringTokenizer non alloca il risultato in anticipo; split sì.
  • 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:

java— editable, runs on the server

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.

Esercitati

Pratica
Quale delle seguenti rappresenta una **differenza comportamentale reale** tra `StringTokenizer` e `String.split`?
Quale delle seguenti rappresenta una **differenza comportamentale reale** tra `StringTokenizer` e `String.split`?
Was this page helpful?