Comparación de cadenas en Java
Compara cadenas en Java con equals, equalsIgnoreCase, compareTo y entiende por qué == compara referencias.
Comparar dos cadenas parece la operación más inocente del lenguaje, pero es donde los programadores Java novatos tropiezan con más frecuencia. La razón es que == es la sintaxis que más a primera vista significa "¿son iguales?" — y para las cadenas, responde una pregunta que casi nunca queremos responder. Este capítulo recorre la API de comparación en el orden en que deberías utilizarla, y termina con las reglas de ordenación: plegado de mayúsculas y minúsculas, locale, cadenas numéricas, y la trampa de confiar en el orden predeterminado de la JVM.
Por qué == es incorrecto para cadenas
== compara referencias de objetos. Pregunta: "¿apuntan estas dos variables exactamente al mismo objeto en el heap?". Para las cadenas que comparten identidad a través del pool de cadenas, == puede devolver true. Para cualquier otra cosa — cadenas construidas en tiempo de ejecución, cadenas analizadas desde la entrada, cadenas devueltas por new String(...) — devuelve false incluso cuando el contenido es idéntico.
String a = "hello";
String b = "hello";
String c = new String("hello");
String d = "hel" + new String("lo");
a == b; // true — both pooled literals
a == c; // false — c is a fresh object
a == d; // false — d is built at runtime
a.equals(c); // true — contents match
a.equals(d); // true — contents matchLa regla es breve y absoluta: para la igualdad de cadenas, usa equals. Siempre. Sin importar de dónde vengan las cadenas. == "funciona casualmente" en las primeras pruebas con literales, pero falla silenciosamente en cuanto una cadena pasa por I/O.
equals y equalsIgnoreCase
equals devuelve true si y solo si las dos cadenas tienen los mismos caracteres en el mismo orden:
"hello".equals("hello"); // true
"hello".equals("Hello"); // false — case-sensitive
"hello".equals(null); // false — never throwsequalsIgnoreCase hace una comparación carácter a carácter tras el plegado de mayúsculas y minúsculas Unicode:
"hello".equalsIgnoreCase("HELLO"); // true
"hello".equalsIgnoreCase("Hello"); // true
"straße".equalsIgnoreCase("STRASSE"); // false — ß and SS differ in lengthEl ejemplo con la ß alemana es una advertencia útil: el plegado de mayúsculas y minúsculas en Unicode no es una biyección (ß se pasa a minúsculas como ella misma, pero en algunos contratos también coincide con ss). Si la entrada del usuario puede contener texto no ASCII y necesitas una coincidencia sin distinción de mayúsculas y minúsculas, es preferible normalizar ambos lados con String#toLowerCase(Locale) antes de comparar, o usar java.text.Collator para una coincidencia sensible al locale.
Comparación segura con null
equals sobre un receptor null lanza NullPointerException. Por tanto, esto es un error:
if (input.equals("admin")) { ... } // NPE when input is nullTres soluciones idiomáticas:
"admin".equals(input) // literal on the left — handles null safely
Objects.equals(input, "admin") // null-safe symmetric comparison (java.util.Objects)
Objects.requireNonNull(input).equals("admin") // fail-fast if null is a bug"literal".equals(variable) — a veces llamado comparación Yoda — es la más habitual en bases de código consolidadas. Objects.equals es algo más elegante cuando ninguno de los lados se conoce estáticamente.
compareTo y compareToIgnoreCase
Cuando necesitas orden (ordenar, comprobar rangos, búsqueda binaria), compareTo devuelve un int:
< 0si el receptor ordena antes que el argumento0si son iguales> 0si el receptor ordena después que el argumento
"apple".compareTo("banana"); // negative
"banana".compareTo("apple"); // positive
"apple".compareTo("apple"); // 0La comparación es por unidad de código Unicode (UTF-16), carácter a carácter, con las cadenas más cortas ordenando antes que las más largas cuando una es prefijo de la otra. Eso da un orden total definido — pero no el orden que un humano llamaría "alfabético":
"Z".compareTo("a"); // negative — 'Z' is 0x5A, 'a' is 0x61
"apple".compareTo("Banana"); // positive — uppercase letters come first in ASCIIPara una ordenación orientada al usuario, hay dos respuestas reales:
compareToIgnoreCasepara la solución económica y orientada a ASCII que funciona bien con el inglés.java.text.Collatorpara una ordenación correctamente sensible al locale que maneja acentos, laßalemana, el lugar correcto parañen español, y la convención de que algunos sistemas de escritura colocan los numerales después de las letras.
Collator c = Collator.getInstance(Locale.FRENCH);
List<String> names = new ArrayList<>(List.of("éclair", "Étoile", "anvil"));
names.sort(c); // ["anvil", "éclair", "Étoile"] — proper French sortSi el usuario va a ver la lista, usa Collator. Si es una clave de ordenación en un índice interno, compareTo es más rápido y estable.
contentEquals y CharSequence
String#contentEquals compara con cualquier CharSequence — String, StringBuilder, StringBuffer, o un CharBuffer. Es el método adecuado cuando quieres saber si el contenido actual de un builder es igual a una cadena conocida sin una llamada intermedia a toString():
StringBuilder sb = new StringBuilder("hello");
"hello".contentEquals(sb); // true — no toString allocationequals no sirve aquí, porque String#equals devuelve false para cualquier argumento que no sea un String por contrato.
equals ignora completamente el pool
El pool es una optimización de almacenamiento interna; equals no lo consulta. Dos cadenas con los mismos caracteres se comparan como iguales independientemente de si viven en la misma dirección o no:
String pooled = "x";
String fresh = new String("x");
pooled == fresh; // false — different objects
pooled.equals(fresh); // true — same contents
fresh.hashCode() == pooled.hashCode(); // true — equal strings always hash the sameEsa última línea es la razón por la que String es seguro para usar como clave de HashMap: las cadenas iguales siempre tienen códigos hash iguales, y la caché hace que las búsquedas sean económicas.
Un ejemplo práctico
El programa recorre las decisiones de comparación que tomarás en código real: elige el método correcto para la igualdad, maneja null, ordena con el comparador adecuado. La salida muestra visualmente las diferencias entre compareTo y un Collator lado a lado.
Las tres ordenaciones hacen visibles las diferencias. compareTo da [Banana, anvil, apple, Étoile, éclair]: la Banana en mayúsculas salta al frente porque B (0x42) ordena antes que cualquier letra minúscula, y Étoile queda cerca del final porque É (0xC9) está por encima de z. CASE_INSENSITIVE_ORDER produce [anvil, apple, Banana, éclair, Étoile], plegando las mayúsculas para que la lista se lea alfabéticamente. En esta lista concreta el Collator francés produce el mismo orden — pero es el único cuyo resultado es correcto por construcción: pliega mayúsculas y trata los acentos como una distinción secundaria, por lo que sigue siendo correcto con entradas donde el truco de los puntos de código falla (por ejemplo, ordenar côté respecto a cote, o situar correctamente ñ en español).
Qué sigue
Dividir una cadena en partes es el tema natural a continuación. Java tiene dos herramientas para ello, y una de ellas es más antigua de lo que el diseño del lenguaje quisiera que recordaras. Continúa con Java StringTokenizer.