Comparaison d'Objets en Java : Eviter les Pieges avec == et equals()

Maitrisez la comparaison d'objets en Java. Decouvrez pourquoi l'operateur == peut vous induire en erreur et comment utiliser correctement equals() pour comparer String, Integer et autres objets.

Mahmoud DEVO
Mahmoud DEVO
December 27, 2025 7 min read
Comparaison d'Objets en Java : Eviter les Pieges avec == et equals()

Introduction

La comparaison d’objets en Java est l’un des sujets qui cause le plus de confusion chez les developpeurs, qu’ils soient debutants ou meme experimentes. Cette confusion provient principalement de la difference fondamentale entre la comparaison de references (avec ==) et la comparaison de valeurs (avec equals()).

Lorsque vous developpez des applications Java, il est facile de tomber dans les pieges courants qui peuvent causer des bugs subtils et difficiles a detecter. Ces erreurs sont particulierement insidieuses car elles peuvent fonctionner dans certains cas et echouer dans d’autres, rendant le debogage extremement frustrant.

Dans cet article, nous allons explorer en profondeur :

  • La difference entre == et equals()
  • Le fonctionnement du cache d’Integer et du pool de String
  • Les pieges courants a eviter absolument
  • Les bonnes pratiques pour une comparaison fiable

Que vous prepariez un entretien technique ou que vous souhaitiez solidifier vos connaissances Java, cet article vous donnera toutes les cles pour maitriser ce sujet essentiel.

Comprendre l’Operateur == en Java

L’operateur == en Java se comporte differemment selon le type de donnees que vous comparez :

  • Pour les types primitifs (int, char, boolean, etc.) : il compare les valeurs
  • Pour les objets (String, Integer, etc.) : il compare les references memoire

Cette distinction est cruciale et source de nombreuses erreurs.

Comparaison de Types Primitifs vs Objets

// Types primitifs - comparaison de valeurs
int a = 5;
int b = 5;
System.out.println(a == b);  // true - les valeurs sont egales

// Objets - comparaison de references
Integer x = new Integer(5);
Integer y = new Integer(5);
System.out.println(x == y);  // false - references differentes !
System.out.println(x.equals(y));  // true - valeurs egales

Dans cet exemple, meme si x et y contiennent la meme valeur (5), x == y retourne false car ce sont deux objets distincts en memoire.

Le Cache d’Integer : Un Piege Subtil

Exemple : Comparaison d’entiers

Prenons l’exemple suivant qui peut sembler contradictoire :

Integer int1_1 = Integer.valueOf("1");
Integer int1_2 = Integer.valueOf(1);
System.out.println("int1_1 == int1_2: " + (int1_1 == int1_2)); // true
System.out.println("int1_1 equals int1_2: " + int1_1.equals(int1_2)); // true

// Mais attention avec des valeurs plus grandes !
Integer big1 = Integer.valueOf(200);
Integer big2 = Integer.valueOf(200);
System.out.println("big1 == big2: " + (big1 == big2)); // false !
System.out.println("big1 equals big2: " + big1.equals(big2)); // true

Pourquoi 1 == 1 fonctionne mais 200 == 200 echoue ?

Le Integer Cache en Detail

La JVM maintient un cache d’objets Integer pour les valeurs comprises entre -128 et 127 (par defaut). Ce mecanisme d’optimisation est defini dans la specification Java.

// Code source simplifie du cache Integer
private static class IntegerCache {
    static final int low = -128;
    static final int high = 127;  // Peut etre modifie via JVM argument
    static final Integer cache[];

    static {
        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);
    }
}

Demonstration Complete du Cache

public class IntegerCacheDemo {
    public static void main(String[] args) {
        // Dans la plage du cache (-128 a 127)
        Integer a = 127;
        Integer b = 127;
        System.out.println("127 == 127 : " + (a == b));  // true

        // Hors de la plage du cache
        Integer c = 128;
        Integer d = 128;
        System.out.println("128 == 128 : " + (c == d));  // false

        // Limite inferieure
        Integer e = -128;
        Integer f = -128;
        System.out.println("-128 == -128 : " + (e == f));  // true

        Integer g = -129;
        Integer h = -129;
        System.out.println("-129 == -129 : " + (g == h));  // false
    }
}

## Le String Pool : Comprendre l'Internement des Chaines

La comparaison de chaines de caracteres presente des comportements similaires mais avec ses propres subtilites.

### Le Mecanisme du String Pool

Java maintient un **pool de chaines** (String Pool) dans la memoire heap. Quand vous creez une chaine litterale, Java verifie d'abord si elle existe deja dans le pool.

```java
String s1 = "hello";  // Cree dans le String Pool
String s2 = "hello";  // Reference la meme instance du pool
String s3 = new String("hello");  // Nouvel objet hors du pool

System.out.println("s1 == s2: " + (s1 == s2));  // true - meme reference
System.out.println("s1 == s3: " + (s1 == s3));  // false - references differentes
System.out.println("s1.equals(s3): " + s1.equals(s3));  // true - memes valeurs

Visualisation en Memoire

String Pool (Heap)          Heap (hors pool)
+---------------+           +---------------+
|   "hello"     | <---+     |   "hello"     |
+---------------+     |     +---------------+
        ^             |             ^
        |             |             |
       s1            s2            s3

La Methode intern()

Vous pouvez forcer une chaine a rejoindre le pool avec intern() :

String s1 = "hello";
String s3 = new String("hello");
String s4 = s3.intern();  // Retourne la reference du pool

System.out.println("s1 == s3: " + (s1 == s3));  // false
System.out.println("s1 == s4: " + (s1 == s4));  // true - s4 pointe vers le pool

Concatenation et String Pool

Attention au comportement avec la concatenation :

String s1 = "hello";
String s2 = "hel" + "lo";  // Optimise par le compilateur
String s3 = "hel";
String s4 = s3 + "lo";  // Concatenation a l'execution

System.out.println("s1 == s2: " + (s1 == s2));  // true - compile-time constant
System.out.println("s1 == s4: " + (s1 == s4));  // false - runtime concatenation

Bonnes Pratiques pour la Comparaison d’Objets

Regle d’Or : Toujours Utiliser equals()

Pour comparer correctement les objets en Java, utilisez toujours la methode equals() au lieu de l’operateur ==.

// MAUVAIS - Ne faites jamais cela pour comparer des objets
if (str1 == str2) { ... }
if (integer1 == integer2) { ... }

// BON - Utilisez toujours equals()
if (str1.equals(str2)) { ... }
if (integer1.equals(integer2)) { ... }

Gerer les Valeurs Null

Attention au NullPointerException lors de l’utilisation de equals() :

String s1 = null;
String s2 = "hello";

// DANGER - Lance NullPointerException
// s1.equals(s2);

// SOLUTION 1 : Verifier null d'abord
if (s1 != null && s1.equals(s2)) { ... }

// SOLUTION 2 : Inverser l'ordre (si s2 n'est jamais null)
if ("hello".equals(s1)) { ... }

// SOLUTION 3 : Utiliser Objects.equals() (Java 7+)
if (Objects.equals(s1, s2)) { ... }  // Gere null automatiquement

La Methode Objects.equals()

Depuis Java 7, Objects.equals() est la methode recommandee :

import java.util.Objects;

String a = null;
String b = null;
String c = "hello";

System.out.println(Objects.equals(a, b));  // true - les deux sont null
System.out.println(Objects.equals(a, c));  // false - pas d'exception
System.out.println(Objects.equals(c, c));  // true

Implementer equals() Correctement

Si vous creez vos propres classes, implementez equals() et hashCode() :

public class Person {
    private String name;
    private int age;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;  // Meme reference
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

Pieges Courants a Eviter

Piege 1 : Autoboxing Trompeur

Integer a = 100;
Integer b = 100;
System.out.println(a == b);  // true (dans le cache)

Integer c = 1000;
Integer d = 1000;
System.out.println(c == d);  // false (hors cache)

// Le meme code avec des resultats differents selon les valeurs !

Piege 2 : Comparaison avec null

String s = null;

// ERREUR COURANTE
if (s.equals("test")) { }  // NullPointerException !

// CORRECT
if ("test".equals(s)) { }  // Retourne false, pas d'exception

Piege 3 : Comparer des Types Differents

Integer i = 42;
Long l = 42L;

System.out.println(i.equals(l));  // false ! Types differents
System.out.println(i.intValue() == l.intValue());  // true

Piege 4 : equals() Non Symetrique

// Mauvaise implementation
class BadPerson {
    String name;

    @Override
    public boolean equals(Object o) {
        if (o instanceof String) {
            return name.equals(o);  // MAUVAIS !
        }
        // ...
    }
}

// Violation de la symetrie : person.equals(string) != string.equals(person)

Piege 5 : Oublier hashCode()

// Si vous redefinissez equals(), vous DEVEZ redefinir hashCode()
// Sinon les collections (HashMap, HashSet) ne fonctionneront pas correctement

Set<Person> personSet = new HashSet<>();
Person p1 = new Person("John", 30);
personSet.add(p1);

Person p2 = new Person("John", 30);
System.out.println(personSet.contains(p2));
// false si hashCode() n'est pas implemente !

Resume Comparatif

Scenario==equals()Recommandation
Types primitifsCompare les valeursN/AUtilisez ==
String litterauxPeut fonctionner (pool)Toujours correctUtilisez equals()
new String()Ne fonctionne pasToujours correctUtilisez equals()
Integer (-128 a 127)Peut fonctionner (cache)Toujours correctUtilisez equals()
Integer (hors cache)Ne fonctionne pasToujours correctUtilisez equals()
Objets personnalisesCompare referencesCompare valeursUtilisez equals()

Conclusion

La comparaison d’objets en Java est un sujet fondamental qui peut causer des bugs subtils si mal compris. Les points essentiels a retenir sont :

  1. L’operateur == compare les references memoire pour les objets, pas les valeurs
  2. Le cache Integer (-128 a 127) peut donner l’illusion que == fonctionne
  3. Le String Pool peut egalement induire en erreur avec les chaines litterales
  4. Utilisez toujours equals() pour comparer les valeurs des objets
  5. Preferez Objects.equals() pour gerer automatiquement les valeurs null
  6. Implementez hashCode() chaque fois que vous redefinissez equals()

En suivant ces bonnes pratiques, vous eviterez les bugs les plus courants lies a la comparaison d’objets et produirez un code plus robuste et maintenable.

Pour Aller Plus Loin

  • Contrat equals/hashCode : Etudiez les regles du contrat entre ces deux methodes
  • Comparator et Comparable : Pour les comparaisons d’ordre (tri)
  • Records Java 14+ : Les records implementent automatiquement equals() et hashCode()
  • Project Lombok : Annotations @EqualsAndHashCode pour generer ces methodes automatiquement
Advertisement

In-Article Ad

Dev Mode

Share this article

Mahmoud DEVO

Mahmoud DEVO

Senior Full-Stack Developer

I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.

Enjoyed this article?

Subscribe to get more tech content delivered to your inbox.

Related Articles