W3docs

Comparaison de chaînes en Java

Comparez correctement les chaînes Java avec equals, equalsIgnoreCase, compareTo et comprenez pourquoi == compare les références.

Comparer deux chaînes de caractères semble être l'opération la plus anodine du langage, mais c'est là que les nouveaux programmeurs Java trébuchent le plus souvent. La raison est que == constitue la syntaxe la plus évidente pour « sont-ils identiques ? » — et pour les chaînes, elle répond à une question que vous ne voulez presque jamais poser. Ce chapitre parcourt l'API de comparaison dans l'ordre dans lequel vous devriez y faire appel, et se termine par les règles d'ordonnancement — correspondance de casse, locale, chaînes numériques et le piège de se fier au tri par défaut de la JVM.

Pourquoi == est incorrect pour les chaînes

== compare des références pour les objets. Elle demande « ces deux variables pointent-elles vers exactement le même objet sur le tas ? ». Pour les chaînes qui partagent leur identité via le string pool, == retourne true par chance. Pour tout le reste — les chaînes construites à l'exécution, les chaînes issues d'une entrée utilisateur, les chaînes renvoyées par new String(...) — elle retourne false même lorsque le contenu est identique.

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 règle est courte et absolue : pour l'égalité des chaînes, utilisez equals. Toujours. Peu importe d'où viennent les chaînes. == « fonctionne par hasard » lors des premiers tests avec des littéraux, puis échoue silencieusement dès qu'une chaîne transite par des entrées/sorties.

equals et equalsIgnoreCase

equals retourne true si et seulement si les deux chaînes ont les mêmes caractères dans le même ordre :

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

equalsIgnoreCase effectue une comparaison caractère par caractère après normalisation de casse Unicode :

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

L'exemple du ß allemand constitue un avertissement utile : la normalisation de casse en Unicode n'est pas une bijection (ß se met en minuscule en lui-même, mais correspond aussi à ss dans certains contextes). Si la saisie utilisateur peut contenir du texte non-ASCII et que vous avez besoin d'une correspondance insensible à la casse, préférez normaliser les deux côtés avec String#toLowerCase(Locale) avant de comparer, ou utilisez java.text.Collator pour une correspondance tenant compte de la locale.

Comparaison sécurisée face à null

equals sur un récepteur null lève une NullPointerException. Voici donc un bug :

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

Trois corrections idiomatiques :

"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

"littéral".equals(variable) — parfois appelé comparaison Yoda — est la forme la plus courante dans les bases de code établies. Objects.equals est légèrement plus élégant lorsque ni l'un ni l'autre côté n'est connu statiquement.

compareTo et compareToIgnoreCase

Lorsque vous avez besoin d'un ordre (tri, vérification de plage, recherche binaire), compareTo retourne un int :

  • < 0 si le récepteur est classé avant l'argument
  • 0 s'ils sont égaux
  • > 0 si le récepteur est classé après l'argument
"apple".compareTo("banana");        // negative
"banana".compareTo("apple");        // positive
"apple".compareTo("apple");         // 0

La comparaison s'effectue par unité de code Unicode (UTF-16), caractère par caractère, les chaînes plus courtes étant classées avant les plus longues lorsque l'une est un préfixe de l'autre. Cela vous donne un ordre total défini — mais pas l'ordre qu'un humain qualifierait d'« alphabétique » :

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

Pour un tri destiné à des humains, deux vraies solutions :

  • compareToIgnoreCase pour la correction rapide à tendance ASCII qui fonctionne bien pour l'anglais.
  • java.text.Collator pour un tri correctement sensible à la locale qui gère les accents, le ß allemand, la bonne place pour ñ en espagnol, et la convention selon laquelle certains scripts placent les chiffres après les lettres.
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 un utilisateur va voir la liste, utilisez Collator. S'il s'agit d'une clé de tri dans un index interne, compareTo est plus rapide et stable.

contentEquals et CharSequence

String#contentEquals compare avec n'importe quelle CharSequenceString, StringBuilder, StringBuffer ou CharBuffer. C'est la bonne méthode lorsque vous souhaitez savoir si le contenu actuel d'un builder est égal à une chaîne connue sans allocation intermédiaire via toString() :

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

equals ne sera d'aucun secours ici, car String#equals retourne false pour tout argument qui n'est pas une String, par contrat.

equals ignore complètement le pool

Le pool est une optimisation de stockage interne ; equals ne le consulte pas. Deux chaînes avec les mêmes caractères sont considérées égales qu'elles résident ou non à la même adresse :

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

Cette dernière ligne explique pourquoi String est sûre à utiliser comme clé de HashMap : les chaînes égales ont toujours des codes de hachage égaux, et le cache rend les recherches peu coûteuses.

Un exemple concret

Le programme parcourt les décisions de comparaison que vous prendrez dans du code réel : choisir la bonne méthode pour l'égalité, gérer null, trier avec le bon comparateur. La sortie rend visibles côte à côte les différences entre compareTo et un Collator.

java— editable, runs on the server

Les trois tris rendent les différences visibles. compareTo donne [Banana, anvil, apple, Étoile, éclair] : la majuscule Banana saute en tête parce que B (0x42) est classé avant toute lettre minuscule, et Étoile atterrit près de la fin car É (0xC9) est au-dessus de z. CASE_INSENSITIVE_ORDER produit [anvil, apple, Banana, éclair, Étoile], en égalisant la casse pour que la liste soit lue alphabétiquement. Sur cette liste particulière, le Collator français donne le même ordre — mais c'est le seul dont le résultat est correct par construction : il normalise la casse et traite les accents comme une distinction secondaire, de sorte qu'il reste correct sur des entrées où l'astuce du point de code échoue (par exemple, trier côté par rapport à cote, ou placer ñ correctement en espagnol).

Prochaine étape

Diviser une chaîne en morceaux est le sujet naturellement suivant. Java dispose de deux outils pour cela, et l'un d'eux est plus ancien que ce que la conception du langage souhaiterait que vous vous rappeliez. Continuez vers Java StringTokenizer.

Pratique

Pratique
Quel appel pour tester si un `input` (potentiellement `null`) est égal à la chaîne `'admin'` est **à la fois** correct et sécurisé face à `null` ?
Quel appel pour tester si un `input` (potentiellement `null`) est égal à la chaîne `'admin'` est **à la fois** correct et sécurisé face à `null` ?
Was this page helpful?