Egalité et comparaison des chaînes de caractères en Java

La manipulation des chaînes de caractères est une composante importante de tout programme Java. Il est donc important de savoir déterminer leur égalité et, par extension, de pouvoir les comparer entre elles.

Mais beaucoup de paramètres entrent en jeu, comme leur présence dans le pool des chaînes de caractères, leur casse (majuscule/minuscule) et l'accentuation de leurs caractères. Nous allons voir ou revoir ici les mécanismes et possibilités du langage Java dans ce domaine.

Egalité des chaînes

Rappel : le pool de valeurs littérales

La gestion des chaînes de caractères occupe une place importante dans les programmes. Le langage Java a donc été optimisé sur ce point, et permet de placer les valeurs littérales dans un pool spécial, afin de limiter les doublons en mémoire.

Lors de la déclaration d'une référence de type String, deux situations se présentent :

  • si une valeur littérale y est directement associée, cette dernière sera automatiquement placée dans le pool et la référence pointera dessus.
  • si une nouvelle instance de String est créée, la valeur qu'elle représente ne sera pas poolée.
  1. /* La chaîne "Hello" sera placée dans le pool, et la référence s1 pointe dessus. */
  2. String s1 = "Hello";
  3. /* La chaîne "World" ne sera pas placée dans le pool. La référence s2 pointe vers une instance de String. */
  4. String s2 = new String("World");

Il est toutefois possible de demander la mise en pool d'une chaîne de caractères, grâce à la méthode "intern()" :

  1. /* La chaîne "World" est maintenant placée en pool, et s2 pointe dessus. */
  2. s2 = s2.intern();

Egalité des chaînes poolées

Puisque toutes les références pointant vers une même chaîne poolée pointent réellement vers la même adresse mémoire, il est facile de les comparer avec l'opérateur d'égalité d'adresse "==".

  1. /* Les deux références s1 et s2 pointent vers la même chaîne poolée. */
  2. String s1 = "Hello World";
  3. String s2 = "Hello World";
  4. /* La référence s3 pointe vers une instance de String, dont la chaîne n'est pas poolée. */
  5. String s3 = new String("Hello World");
  6.  
  7. /* Vérifions que s1 et s2 ont la même adresse, mais que s3 pointe sur une adresse différence. */
  8. System.out.println(s1 == s2); /* true */
  9. System.out.println(s1 == s3); /* false */
  10.  
  11. /* Plaçons la chaîne représentée par s3 dans le pool. */
  12. s3 = s3.intern();
  13. /* Maintenant, s3 pointe vers la même adresse que s1 et s2, car leur chaîne poolée est identique. */
  14. System.out.println(s1 == s3); /* true */

Cette méthode est utile, mais présente ses limites :

  • Tout d'abord, seules deux chaînes strictement identiques sont placées à la même adresse dans le pool, et sont donc déclarées égales par l'opérateur "==".
  • Ensuite, il n'est pas bon de placer toutes les chaînes manipulées par un programme dans le pool, car cela pourrait entraîner une consommation mémoire excessive. Il est préférable de se limiter aux chaînes récurrentes et utilisées telles quelles (c'est-à-dire non utilisées dans des concaténations, par exemple).

Egalité des chaînes non poolées

La méthode la plus fréquente pour déterminer l'égalité de deux chaînes est d'utiliser les méthodes de la classe String : "equals()" et "equalsIgnoreCase()". Ces méthodes présentent en outre l'avantage de fonctionner de manière identique, que la chaîne représentée soit poolée ou non.

  1. /* Les références s1 et s2 représentent la même chaîne */
  2. String s1 = "Hello World";
  3. String s2 = new String("Hello World");
  4. /* La référence s3 représente la même chaîne que les deux précédentes, mais avec une casse différente. */
  5. String s3 = new String("hello world");
  6.  
  7. /* Egalité avec equals() et equalsIgnoreCase() */
  8. System.out.println(s1.equals(s2)); /* true */
  9. System.out.println(s1.equals(s3)); /* false */
  10. System.out.println(s1.equalsIgnoreCase(s3)); /* true */

Les chaînes identiques mais accentuées différemment sont considérées comme différentes, mais il existe un mécanisme permettant de contourner ce problème, que nous verrons à la fin de cet article.

Comparaison des chaînes

Il est souvent nécessaire de trier des chaînes de caractères, plutôt que de simplement déterminer leur égalité. Il faut donc pouvoir les comparer entre elles. Pour cela, deux possibilités :

  • Utiliser les méthodes de la classe String elle-même
  • Utiliser des Comparateurs

Utilisation des méthodes de la classe String

La classe String implémente l'interface java.lang.Comparable, et possède donc une méthode "compareTo(String s)" renvoyant :

  • un nombre négatif si la chaîne actuelle est placée avant la chaîne passée en paramètre;
  • zéro (0) si les deux chaînes sont strictement égales;
  • un nombre positif si la chaîne actuelle est placée après la chaîne passée en paramètre.
  1. int comparaison = "Hello".compareTo("World");
  2. System.out.println(comparaison); /* Nombre négatif car H < W */

La comparaison se base sur la valeur Unicode des caractères qui composent les chaînes. Pour rappel, les espaces sont situés avant les chiffres, les chiffres avant les lettres, et parmi les lettres, les majuscules avant les minuscules.

<espaces> < 0-9 < A-Z < a-z

La classe String fournit également une méthode "compareToIgnoreCase(String s)", permettant de comparer deux chaînes sans prendre en compte leur casse :

  1. int comparaison = "Hello World".compareToIgnoreCase("hello world");
  2. System.out.println(comparaison); /* zéro */

Utilisation de Comparateurs externes

Les comparateurs prennent deux chaînes en paramètre, et renvoient comme résultat un entier représentant la position relative des deux chaînes, exactement comme décrit dans le chapitre précédent.

La méthode "compareToIgnoreCase(String s)" vue plus haut utilise en réalité un comparateur nommé "CASE_INSENSITIVE_ORDER", qui est une classe interne (publique et statique) de la classe String. Il est donc possible de l'utiliser indépendamment :

  1. Comparator comparator = String.CASE_INSENSITIVE_ORDER;
  2. int comparaison = comparator.compare("Hello World", "hello world");
  3. System.out.println(comparaison); /* zéro */

La différence de casse n'est donc pas un problème majeur pour comparer les chaînes de caractères. Mais les problèmes dus à l'accentuation des caractères demeure. Or, il s'agit d'un problème fréquent : qui n'a jamais eu besoin de déterminer l'égalité de deux adresses postales ou deux patronymes accentués différemment ? La classe java.text.Collator à la rescousse.

Cette classe implémente les règles de décomposition Unicode permettant de déterminer à quel point des caractères diffèrent. Il existe trois niveaux de différences :

  • Primaire : désigne deux caractères totalement différents (ex: A et Z)
  • Secondaire : désigne deux caractères de même base mais accentués différemment (ex: a et à)
  • Tertiaire : désigne deux caractères de même base, accentués identiquement, mais de casse différente (ex: a et A)

Les exemples donnés ici correspondent au Français, mais pourraient différer légèrement dans d'autres langues.

Pour comparer deux chaînes, il suffit donc de sélectionner une Locale et un niveau de comparaison, et d'utiliser un Collator comme un Comparator :

  1. /* PRIMARY = deux caractères ne sont différents que s'ils ont une base différente.
  2. On ignore donc les différences de casse et d'accentuation. */
  3. Collator collator = Collator.getInstance(Locale.FRENCH);
  4. collator.setStrength(Collator.PRIMARY);
  5. int comparaison = collator.compare("L'été à la plage", "l'ete a la plage");
  6. System.out.println(comparaison);

Les Collators ne sont malheureusement pas utilisables directement par le framework Collection, pour les opérations de tri. Mais il est facile de créer un Comparator se basant dessus :

  1. public class AccentuatedStringComparator implements Comparator
  2. {
  3. private final Collator collator;
  4.  
  5. public AccentuatedStringComparator()
  6. {
  7. collator = Collator.getInstance(Locale.FRENCH);
  8. collator.setStrength(Collator.PRIMARY);
  9. }
  10.  
  11. public int compare(Object object1, Object object2)
  12. {
  13. if ((object1 instanceof String) && (object2 instanceof String))
  14. {
  15. String s1 = (String) object1;
  16. String s2 = (String) object2;
  17. return collator.compare(s1, s2);
  18. }
  19. else
  20. {
  21. throw new IllegalArgumentException("Les objets comparés doivent être des String");
  22. }
  23. }
  24. }

Vérifions son utilisation avec un TreeSet :

  1. TreeSet set = new TreeSet(new AccentuatedStringComparator());
  2. set.add("L'été à la plage");
  3. set.add("L'ete a la plage");
  4. set.add("L'ETE A LA PLAGE");
  5. System.out.println(set.size()); /* Taille = 1 */

Les trois chaînes sont considérées identiques par notre comparateur, le Set n'en a donc retenu qu'une seule instance.


Commentaires

1. Le dimanche 24 février 2008, 22:16 par Slider

On en apprend tout les jours un peu plus...

2. Le vendredi 21 mai 2010, 14:49 par lilia

Bonjour,

J'utilise la classe Collator pour comparer des chaînes de caractères à l'aide d'une TreeMap et je rencontre un problème qui est le suivant: les espaces ne sont pas considérés

Collator comp = Collator.getInstance(userLang);
comp.setStrength(Collator.TERTIARY);
TreeMap trie = new TreeMap(comp);

Lorsque j'ajoute mes chaînes de caractères dans la map, elles ne sont pas correctement triées. En fait les espaces ne sont pas considérés alors qu'ils le devraient.

exemple:
"documents maison" est classé avant "document société" alors qu'il devrait être après.

Quelqu'un aurait-il une idée. Merci

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.