Java StringTokenizer
Divide cadenas en tokens con la clase heredada StringTokenizer y descubre cuándo es mejor usar String.split en Java.
java.util.StringTokenizer es la clase original del JDK para "dividir una cadena en partes" — un tokenizador carácter a carácter que forma parte de la plataforma desde la versión 1.0. El propio Javadoc desaconseja su uso en código nuevo: "StringTokenizer es una clase heredada que se conserva por razones de compatibilidad, aunque su uso está desaconsejado en código nuevo. Se recomienda que cualquier persona que busque esta funcionalidad use el método split de String o el paquete java.util.regex en su lugar."
Ese es el mensaje principal, y es correcto — pero StringTokenizer sigue apareciendo en bases de código reales y tiene algunos nichos legítimos. Este capítulo la enseña brevemente y luego explica claramente cuándo es mejor usar String.split o Scanner.
El bucle básico
Un StringTokenizer es una Enumeration de subcadenas:
StringTokenizer st = new StringTokenizer("apple banana cherry");
while (st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
// apple
// banana
// cherryEl conjunto de delimitadores predeterminado es el espacio en blanco — espacio, tabulación, nueva línea, retorno de carro y avance de página. Los delimitadores adyacentes se colapsan: " a b " produce exactamente dos tokens.
Delimitadores personalizados
El segundo constructor recibe una cadena cuyos caracteres se tratan individualmente como delimitadores — no como una cadena delimitadora:
StringTokenizer st = new StringTokenizer("one,two;three|four", ",;|");
while (st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
// one
// two
// three
// fourEste es el detalle de diseño que sorprende a la gente. new StringTokenizer(input, ", ") trata , y el espacio cada uno como un delimitador, no la secuencia de dos caracteres ", ". Si necesitas delimitadores de varios caracteres, has superado las capacidades de StringTokenizer.
Devolver los propios delimitadores
El constructor de tres argumentos controla si los delimitadores se emiten como tokens:
StringTokenizer st = new StringTokenizer("a+b*c", "+*", true);
while (st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
// a
// +
// b
// *
// cEsta es la única característica que String.split no replica directamente: habría que construirla con un Matcher y una expresión regular. Para el análisis de expresiones muy simples — el tipo de cosa que no justifica usar un lexer — esta sobrecarga sigue siendo útil.
Contar los tokens de antemano
countTokens() informa cuántos tokens quedan (es decir, los que se producirían con llamadas repetidas a nextToken()). No los consume.
StringTokenizer st = new StringTokenizer("a b c d");
int n = st.countTokens(); // 4
while (st.hasMoreTokens()) st.nextToken();
n = st.countTokens(); // 0Esto puede ser útil cuando se asigna un array de salida del tamaño correcto — aunque con String.split simplemente llamarías a .split(...).length.
Cambiar el delimitador a mitad de flujo
Una característica menos conocida: nextToken(String newDelims) restablece el conjunto de delimitadores para esa llamada en adelante. Una vez cambiado, el nuevo conjunto persiste para las llamadas posteriores a hasMoreTokens()/nextToken() hasta que se cambie de nuevo.
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);
}Para análisis ad-hoc puntuales esto puede resultar elegante. Para cualquier cosa que deba ser mantenible, es confuso — los lectores no esperan que un conjunto de delimitadores cambie dentro de un bucle.
Por qué String.split suele ser mejor
Las razones para preferir String.split (o Pattern.compile(...).split) en código nuevo:
- Delimitadores con regex reales. Delimitadores de varios caracteres, clases de caracteres, alternancia — todo resulta natural.
StringTokenizersolo maneja delimitadores de un solo carácter. - Los tokens vacíos son visibles.
"a,,b".split(",")devuelve["a", "", "b"].StringTokenizeromite el token vacío silenciosamente. Para entradas con formato CSV, "el segundo campo estaba en blanco" es información que generalmente necesitas. - Devuelve un array. Fácil de indexar, fácil de convertir a
List, fácil de procesar como stream. - Generalmente más rápido bajo JIT gracias al almacenamiento en caché de
Patternpara patrones de división simples. - Más fácil de probar, más fácil de leer. Un bucle con tokenizador parece de 1996;
parts = csv.split(",")expresa la intención claramente.
Por qué podrías seguir usándolo
Una lista breve de casos donde StringTokenizer sigue siendo defendible:
- Procesar en streaming una cadena muy larga donde quieres consumir token a token sin mantener un array con todos ellos.
StringTokenizerno asigna el resultado de antemano;splitsí. - Devolver los delimitadores como tokens para el tokenizador más simple posible, sin necesidad de
Pattern/Matcher. - Mantener código antiguo donde el resto del archivo ya lo usa y la coherencia es lo más amable para el próximo lector.
Para todo lo demás: usa split.
Un ejemplo práctico
El programa a continuación tokeniza tres entradas de tres maneras distintas, lado a lado con las llamadas equivalentes a split, para que las diferencias de comportamiento sean visibles:
Dos conclusiones de la salida. En el caso 1, split detecta la celda vacía entre green y blue ([red, green, , blue]); el tokenizador la colapsa ([red, green, blue]). En el caso 2, ambos producen los mismos tokens, pero por razones distintas: el tokenizador divide mix en cada ; y cada espacio de forma independiente ("; " significa "cualquiera de los dos caracteres es un delimitador"), mientras que split("; ") coincide con la secuencia literal de dos caracteres "; ". Aquí coinciden solo porque los separadores en mix son exactamente ; . Cambia la entrada a "k1=v1 ;k2=v2" y los dos divergen inmediatamente. Cualquiera que sea el comportamiento que necesites, el código debe ser explícito al respecto — y eso es mucho más sencillo con split.
Qué viene a continuación
Hasta ahora hemos dividido cadenas en partes. A menudo lo que realmente necesitas es convertir una cadena en un número, un boolean u otro tipo primitivo — y en la dirección opuesta, primitivos de vuelta a cadenas. Ese proceso de ida y vuelta tiene su propio conjunto de helpers y particularidades. Continúa en Conversiones de cadenas en Java.