Comparaison de Valeurs en Java : Pieges des Operateurs == et equals()

Evitez les pieges des operateurs == et != en Java. Comparez correctement entiers, flottants, booleens et objets avec des exemples pratiques et clairs.

Mahmoud DEVO
Mahmoud DEVO
December 27, 2025 7 min read
Comparaison de Valeurs en Java : Pieges des Operateurs == et equals()

Les operateurs de comparaison en Java : un guide pour les developpeurs avances

Introduction

Les operateurs de comparaison en Java sont parmi les elements les plus fondamentaux du langage, utilises dans pratiquement chaque programme que vous ecrirez. Pourtant, derriere leur apparente simplicite se cachent des subtilites qui peuvent conduire a des bugs difficiles a detecter, meme pour des developpeurs experimentes.

Que vous compariez des nombres primitifs, des objets String, ou des valeurs booleennes, la maniere dont Java evalue l’egalite peut varier considerablement selon le contexte. Une mauvaise comprehension de ces mecanismes peut entrainer des comportements inattendus, des comparaisons qui echouent mysterieusement, ou des conditions qui ne s’executent jamais comme prevu.

Dans cet article approfondi, nous allons explorer en detail les differents types d’operateurs de comparaison en Java, comprendre leur fonctionnement interne, et apprendre a les utiliser correctement dans vos projets. Nous examinerons egalement les pieges courants et les bonnes pratiques pour ecrire un code robuste et maintenable.

Les operateurs == et !=

Les operateurs == et != sont utilises pour comparer les valeurs de deux expressions. Cependant, il est important de noter que ces operateurs ne fonctionnent pas toujours comme on pourrait s’y attendre. Leur comportement depend fondamentalement du type des operandes : primitifs ou objets.

Pour les types primitifs (int, long, float, double, boolean, char, byte, short), l’operateur == compare directement les valeurs binaires. Pour les types reference (objets), il compare les adresses memoire, c’est-a-dire s’il s’agit de la meme instance.

Comparaison de nombres entiers

Lorsque les operandes sont des nombres entiers (int ou long), la comparaison est effectuee en verifiant si les valeurs sont identiques. C’est le cas le plus simple et le plus intuitif.

int a = 5;
int b = 5;

System.out.println(a == b); // true - les valeurs sont identiques

int c = 10;
int d = 20;

System.out.println(c == d); // false - les valeurs sont differentes
System.out.println(c != d); // true - les valeurs ne sont pas egales

Attention cependant avec les types wrapper comme Integer. Le comportement change radicalement :

Integer x = 127;
Integer y = 127;
System.out.println(x == y); // true - grace au cache Integer (-128 a 127)

Integer p = 128;
Integer q = 128;
System.out.println(p == q); // false - hors du cache, objets differents
System.out.println(p.equals(q)); // true - comparaison par valeur

Comparaison de nombres flottants

Lorsque les operandes sont des nombres flottants (float ou double), la comparaison est effectuee en verifiant si les representations binaires IEEE 754 sont identiques. C’est l’un des aspects les plus delicats de la comparaison en Java, car les nombres a virgule flottante ne peuvent pas representer exactement toutes les valeurs decimales.

// Exemple de precision flottante
double a = 0.1 + 0.2;
double b = 0.3;

System.out.println(a == b); // false ! Car 0.1 + 0.2 = 0.30000000000000004
System.out.println("a = " + a); // 0.30000000000000004
System.out.println("b = " + b); // 0.3

La bonne approche pour comparer des nombres flottants est d’utiliser une tolerance (epsilon) :

public static boolean areEqual(double a, double b, double epsilon) {
    return Math.abs(a - b) < epsilon;
}

// Utilisation
double x = 0.1 + 0.2;
double y = 0.3;
double epsilon = 1e-10;

System.out.println(areEqual(x, y, epsilon)); // true

Pour une precision maximale, utilisez BigDecimal :

import java.math.BigDecimal;

BigDecimal bd1 = new BigDecimal("0.1").add(new BigDecimal("0.2"));
BigDecimal bd2 = new BigDecimal("0.3");

System.out.println(bd1.compareTo(bd2) == 0); // true

Comparaison de valeurs NaN

Les valeurs NaN (Not a Number) sont specialement gerees par les operateurs de comparaison en Java. C’est un cas unique ou une valeur n’est pas egale a elle-meme ! Si l’un des operandes est une valeur NaN, le resultat de la comparaison est toujours faux.

float nanValue = Float.NaN;

// NaN n'est egal a rien, meme pas a lui-meme
System.out.println(nanValue == nanValue); // false !
System.out.println(nanValue != nanValue); // true !

// Pour tester si une valeur est NaN, utilisez les methodes dediees
System.out.println(Float.isNaN(nanValue)); // true
System.out.println(Double.isNaN(Double.NaN)); // true

Les valeurs Infinity ont egalement un comportement specifique :

double posInf = Double.POSITIVE_INFINITY;
double negInf = Double.NEGATIVE_INFINITY;

System.out.println(posInf == posInf); // true
System.out.println(posInf > negInf); // true
System.out.println(1.0 / 0.0 == posInf); // true

Les operateurs d’egalite pour les booleens

Les operateurs == et != peuvent egalement etre utilises avec des valeurs booleennes (boolean ou Boolean). La distinction entre le type primitif et le type wrapper est cruciale.

// Types primitifs - comparaison directe
boolean a = true;
boolean b = true;
System.out.println(a == b); // true

// Type primitif vs wrapper - autoboxing
boolean c = true;
Boolean d = Boolean.TRUE;
System.out.println(c == d); // true - grace a l'unboxing automatique

// Deux wrappers
Boolean e = Boolean.TRUE;
Boolean f = Boolean.TRUE;
System.out.println(e == f); // true - Boolean.TRUE est une constante partagee

// Attention au new Boolean (deprecie depuis Java 9)
Boolean g = new Boolean(true);
Boolean h = new Boolean(true);
System.out.println(g == h); // false - deux instances differentes
System.out.println(g.equals(h)); // true - comparaison par valeur

La bonne pratique est d’utiliser Boolean.valueOf() ou l’autoboxing plutot que new Boolean() :

Boolean correcte = Boolean.valueOf(true); // Recommande
Boolean aussi = true; // Autoboxing, equivalent

Les operateurs d’egalite pour les objets

Les operateurs == et != appliques aux objets ne verifient pas l’egalite de valeurs, mais l’identite : ils testent si les deux references pointent vers la meme instance en memoire. C’est une distinction fondamentale que tout developpeur Java doit maitriser.

// Exemple avec String
String a = "Hello";
String b = new String("Hello");
String c = "Hello";

System.out.println(a == b); // false - b est une nouvelle instance
System.out.println(a == c); // true - String pool : meme reference
System.out.println(a.equals(b)); // true - meme contenu

La methode equals() et son contrat

La methode equals() est heritee de Object et peut etre surchargee pour definir l’egalite semantique. Son contrat comprend :

  • Reflexivite : x.equals(x) doit retourner true
  • Symetrie : x.equals(y) doit etre equivalent a y.equals(x)
  • Transitivite : si x.equals(y) et y.equals(z), alors x.equals(z)
  • Coherence : appels multiples retournent toujours le meme resultat
  • Non-nullite : x.equals(null) doit retourner false
public class Personne {
    private String nom;
    private int age;

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Personne personne = (Personne) obj;
        return age == personne.age && Objects.equals(nom, personne.nom);
    }

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

Utilisation de Objects.equals()

Depuis Java 7, Objects.equals() offre une methode null-safe pour comparer des objets :

import java.util.Objects;

String s1 = null;
String s2 = "Hello";

// Approche dangereuse - NullPointerException
// s1.equals(s2); // CRASH !

// Approche sure avec Objects.equals()
System.out.println(Objects.equals(s1, s2)); // false, pas d'exception
System.out.println(Objects.equals(null, null)); // true

Bonnes Pratiques

1. Toujours utiliser equals() pour les objets

Sauf si vous avez explicitement besoin de verifier l’identite (meme instance), utilisez toujours equals() pour comparer des objets.

// Correct
if (str1.equals(str2)) { ... }

// Encore mieux - null-safe
if (Objects.equals(str1, str2)) { ... }

// Pour les String, inversez l'ordre si l'un peut etre null
if ("expected".equals(userInput)) { ... }

2. Utiliser une tolerance pour les flottants

Ne comparez jamais des float ou double avec ==. Utilisez toujours une tolerance.

private static final double EPSILON = 1e-9;

public static boolean areEqual(double a, double b) {
    return Math.abs(a - b) < EPSILON;
}

3. Preferer les types primitifs quand possible

Les types primitifs sont plus simples a comparer et evitent les problemes de references.

// Preferer
int count = 5;
if (count == 5) { ... }

// Plutot que
Integer count = 5;
if (count.equals(5)) { ... }

4. Implementer hashCode() avec equals()

Si vous surchargez equals(), vous devez egalement surcharger hashCode() pour respecter le contrat.

// Utiliser les records Java 14+ quand possible
public record Point(int x, int y) {
    // equals() et hashCode() sont generes automatiquement
}

Pieges Courants

Piege 1 : Le cache Integer

Les Integer entre -128 et 127 sont mis en cache. Au-dela, ce sont des instances differentes.

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

Integer c = 200;
Integer d = 200;
System.out.println(c == d); // false - hors cache !

Solution : Toujours utiliser equals() ou comparer avec des primitifs.

Piege 2 : NullPointerException avec equals()

Appeler equals() sur null provoque une exception.

String s = null;
s.equals("test"); // NullPointerException !

Solution : Utilisez Objects.equals() ou inversez l’ordre.

Piege 3 : Comparaison de tableaux

L’operateur == et equals() ne comparent pas le contenu des tableaux.

int[] arr1 = {1, 2, 3};
int[] arr2 = {1, 2, 3};

System.out.println(arr1 == arr2); // false
System.out.println(arr1.equals(arr2)); // false !
System.out.println(Arrays.equals(arr1, arr2)); // true

Solution : Utilisez Arrays.equals() ou Arrays.deepEquals() pour les tableaux multidimensionnels.

Piege 4 : Autoboxing dans les conditions

L’autoboxing peut creer des objets inattendus.

Integer i = null;
if (i == 0) { // NullPointerException lors de l'unboxing !
    // ...
}

Solution : Verifiez toujours null avant de comparer.

if (i != null && i == 0) { ... }
// Ou mieux
if (Integer.valueOf(0).equals(i)) { ... }

Piege 5 : String pool et new String()

String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");

System.out.println(s1 == s2); // true - String pool
System.out.println(s1 == s3); // false - nouvelle instance
System.out.println(s1 == s3.intern()); // true - ajout au pool

Conclusion

Les operateurs de comparaison en Java sont des outils fondamentaux mais subtils qui necessitent une comprehension approfondie pour etre utilises correctement. Dans cet article, nous avons explore :

  • La difference entre == (identite) et equals() (egalite semantique)
  • Les particularites des comparaisons de nombres flottants et la necessite d’utiliser une tolerance
  • Le comportement special des valeurs NaN
  • Les subtilites du cache Integer et du String pool
  • L’importance d’implementer correctement equals() et hashCode()

En suivant les bonnes pratiques presentees et en evitant les pieges courants, vous ecrirez un code plus robuste et plus maintenable. Rappelez-vous : en cas de doute, privilegiez equals() pour les objets et Objects.equals() pour une comparaison null-safe.

Pour aller plus loin

  • Effective Java de Joshua Bloch - Chapitres sur equals() et hashCode()
  • La documentation officielle sur les comparaisons Java
  • Les Records Java 14+ pour une implementation automatique de equals() et hashCode()

N’hesitez pas a experimenter avec ces concepts dans votre propre code. La meilleure facon d’apprendre est de rencontrer (et corriger !) ces pieges par vous-meme.

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