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 matchLa 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 throwsequalsIgnoreCase 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 lengthL'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 nullTre 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:
< 0se il ricevitore è ordinato prima dell'argomento0se sono uguali> 0se il ricevitore è ordinato dopo l'argomento
"apple".compareTo("banana"); // negative
"banana".compareTo("apple"); // positive
"apple".compareTo("apple"); // 0Il 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 ASCIIPer l'ordinamento rivolto agli utenti, due soluzioni concrete:
compareToIgnoreCaseper la soluzione rapida orientata all'ASCII che funziona bene per l'inglese.java.text.Collatorper 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 sortSe 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 CharSequence — String, 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 allocationequals 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 sameQuest'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.
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.