W3docs

Confronto di stringhe in Java

Confronta correttamente le stringhe Java con equals, equalsIgnoreCase, compareTo e scopri perché == confronta i riferimenti.

Confrontare due stringhe sembra l'operazione più innocua del linguaggio, ma è proprio lì che i nuovi programmatori Java inciampano più spesso. Il motivo è che == è la sintassi ovvia per "sono uguali?" — ma per le stringhe risponde a una domanda che quasi mai vuoi porre. Questo capitolo illustra l'API di confronto nell'ordine in cui dovresti utilizzarla e termina con le regole per l'ordinamento: gestione delle maiuscole/minuscole, locale, stringhe numeriche e la trappola di affidarsi all'ordinamento predefinito della JVM.

Perché == è sbagliato per le stringhe

== confronta i riferimenti per gli oggetti. Chiede "queste due variabili puntano esattamente allo stesso oggetto nell'heap?". Per le stringhe che condividono l'identità attraverso il pool di stringhe, == restituisce true per caso. Per tutto il resto — stringhe costruite a runtime, stringhe analizzate dall'input, stringhe restituite da new String(...) — restituisce false anche quando il contenuto è identico.

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 regola è breve e assoluta: per l'uguaglianza delle stringhe, usa equals. Sempre. Indipendentemente dalla provenienza delle stringhe. == "funziona per caso" nei test iniziali con i letterali, poi smette silenziosamente di funzionare non appena una stringa transita attraverso I/O.

equals e equalsIgnoreCase

equals restituisce true se e solo se le due stringhe hanno gli stessi caratteri nello stesso ordine:

"hello".equals("hello");           // true
"hello".equals("Hello");           // false — case-sensitive
"hello".equals(null);              // false — never throws

equalsIgnoreCase esegue un confronto carattere per carattere dopo la normalizzazione Unicode delle maiuscole/minuscole:

"hello".equalsIgnoreCase("HELLO");      // true
"hello".equalsIgnoreCase("Hello");      // true
"straße".equalsIgnoreCase("STRASSE");   // false — ß and SS differ in length

L'esempio con il tedesco ß è un avvertimento utile: la normalizzazione delle maiuscole/minuscole in Unicode non è una biiezione (ß in minuscolo rimane se stesso, ma corrisponde a ss in alcuni contesti). Se l'input dell'utente potrebbe contenere testo non ASCII e hai bisogno di un confronto senza distinzione tra maiuscole e minuscole, preferisci normalizzare entrambi i lati con String#toLowerCase(Locale) prima di confrontare, oppure usa java.text.Collator per un confronto consapevole del locale.

Confronto sicuro con null

equals su un ricevitore null lancia NullPointerException. Questo è quindi un bug:

if (input.equals("admin")) { ... }     // NPE when input is null

Tre correzioni idiomatiche:

"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 volte chiamato confronto Yoda — è il più comune nelle codebase consolidate. Objects.equals è leggermente più elegante quando nessuno dei due lati è noto staticamente.

compareTo e compareToIgnoreCase

Quando vuoi un ordinamento (sort, controllo di intervallo, ricerca binaria), compareTo restituisce un int:

  • < 0 se il ricevitore è ordinato prima dell'argomento
  • 0 se sono uguali
  • > 0 se il ricevitore è ordinato dopo l'argomento
"apple".compareTo("banana");        // negative
"banana".compareTo("apple");        // positive
"apple".compareTo("apple");         // 0

Il confronto avviene per unità di codice Unicode (UTF-16), un carattere alla volta, con le stringhe più corte che precedono quelle più lunghe quando una è prefisso dell'altra. Questo fornisce un ordine totale definito — ma non l'ordine che un essere umano chiamerebbe "alfabetico":

"Z".compareTo("a");                 // negative — 'Z' is 0x5A, 'a' is 0x61
"apple".compareTo("Banana");        // positive — uppercase letters come first in ASCII

Per l'ordinamento rivolto agli utenti, due soluzioni concrete:

  • compareToIgnoreCase per la soluzione rapida orientata all'ASCII che funziona bene per l'inglese.
  • java.text.Collator per un ordinamento correttamente consapevole del locale che gestisce gli accenti, il tedesco ß, la posizione giusta di ñ in spagnolo e la convenzione che alcuni script collocano i numeri dopo le lettere.
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

Se l'utente vedrà la lista, usa Collator. Se è una chiave di ordinamento in un indice interno, compareTo è più veloce e stabile.

contentEquals e CharSequence

String#contentEquals confronta con qualsiasi CharSequenceString, StringBuilder, StringBuffer o un CharBuffer. È il metodo giusto quando vuoi sapere se il contenuto corrente di un builder è uguale a una stringa nota senza una toString() intermedia:

StringBuilder sb = new StringBuilder("hello");
"hello".contentEquals(sb);         // true — no toString allocation

equals non è utile qui, perché String#equals restituisce false per qualsiasi argomento non di tipo String per contratto.

equals ignora completamente il pool

Il pool è un'ottimizzazione di archiviazione interna; equals non lo consulta. Due stringhe con gli stessi caratteri risultano uguali indipendentemente dal fatto che si trovino allo stesso indirizzo:

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

Quest'ultima riga è il motivo per cui String è sicura da usare come chiave di HashMap: stringhe uguali hanno sempre hash code uguali, e la cache rende le ricerche economiche.

Un esempio pratico

Il programma illustra le decisioni di confronto che prenderai nel codice reale: scegli il metodo giusto per l'uguaglianza, gestisci null, ordina con il comparatore corretto. L'output rende visibili le differenze tra compareTo e un Collator affiancati.

java— editable, runs on the server

I tre ordinamenti rendono visibili le differenze. compareTo produce [Banana, anvil, apple, Étoile, éclair]: la Banana maiuscola balza in cima perché B (0x42) precede qualsiasi lettera minuscola, e Étoile finisce quasi in fondo perché É (0xC9) è oltre z. CASE_INSENSITIVE_ORDER produce [anvil, apple, Banana, éclair, Étoile], normalizzando le maiuscole così che la lista risulti alfabetica. Su questo particolare elenco il Collator francese produce lo stesso ordine — ma è l'unico il cui risultato è corretto per costruzione: normalizza le maiuscole e tratta gli accenti come distinzione secondaria, quindi rimane corretto su input dove il trucco dei code point si rompe (ad esempio, ordinando côté rispetto a cote, o posizionando correttamente ñ in spagnolo).

Cosa viene dopo

Dividere una stringa in parti è l'argomento naturale successivo. Java dispone di due strumenti per farlo, e uno di essi è più vecchio di quanto il design del linguaggio voglia che tu ricordi. Continua con Java StringTokenizer.

Pratica

Pratica
Quale chiamata per verificare se un `input` (possibilmente `null`) è uguale alla stringa `'admin'` è **sia** corretta che sicura con `null`?
Quale chiamata per verificare se un `input` (possibilmente `null`) è uguale alla stringa `'admin'` è **sia** corretta che sicura con `null`?
Was this page helpful?