W3docs

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 match

La 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 throws

equalsIgnoreCase 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 length

El 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 null

Tres 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:

  • < 0 si el receptor ordena antes que el argumento
  • 0 si son iguales
  • > 0 si el receptor ordena después que el argumento
"apple".compareTo("banana");        // negative
"banana".compareTo("apple");        // positive
"apple".compareTo("apple");         // 0

La 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 ASCII

Para una ordenación orientada al usuario, hay dos respuestas reales:

  • compareToIgnoreCase para la solución económica y orientada a ASCII que funciona bien con el inglés.
  • java.text.Collator para 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 sort

Si 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 CharSequenceString, 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 allocation

equals 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 same

Esa ú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.

java— editable, runs on the server

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.

Práctica

Práctica
¿Cuál llamada para comprobar si un `input` (posiblemente `null`) es igual a la cadena `'admin'` es **correcta** y a la vez **segura ante null**?
¿Cuál llamada para comprobar si un `input` (posiblemente `null`) es igual a la cadena `'admin'` es **correcta** y a la vez **segura ante null**?
Was this page helpful?