Comment écrire equals () en Java

Feb 22 2023
À la fin de cet article, toutes les informations requises seront couvertes pour pouvoir écrire la meilleure méthode equals () dans le langage de programmation Java. Les détails de l'égalité vont être traités en termes de définition mathématique et à la manière du langage de programmation Java afin de couvrir les informations fondamentales nécessaires pour utiliser la meilleure pratique de la méthode equals ().

À la fin de cet article, toutes les informations requises seront couvertes pour pouvoir écrire la meilleure méthode equals () dans le langage de programmation Java. Les détails de l'égalité vont être traités en termes de définition mathématique et à la manière du langage de programmation Java afin de couvrir les informations fondamentales nécessaires pour utiliser la meilleure pratique de la méthode equals ( ) .

Définition mathématique de l'égalité

Selon Wikipedia , la définition de l'égalité mathématique est

En mathématiques, l'égalité est une relation entre deux quantités ou, plus généralement deux expressions mathématiques, affirmant que les quantités ont la même valeur, ou que les expressions représentent le même objet mathématique. L'égalité entre A et B s'écrit A = B , et prononcée A égale B .

Définition de l'égalité en Java

Dans le langage de programmation Java, l'égalité signifie simplement avoir la même valeur. Il est parfois littéralement censé avoir la même valeur et parfois censé avoir la même structure en termes de propriétés de l'objet.

Lorsqu'il est nécessaire de vérifier l'égalité de deux valeurs de type primitif, le signe égal est directement utilisé comme dans l'exemple ci-dessous ;

int a = 5;
int b = 6;
int c = 5;

// Prints "false" since a and b do not have the same value.
System.out.println(a == b);

// Prints "true" since a and c have the same value.
System.out.println(a == c);

D'autre part, il est parfois nécessaire de vérifier l'égalité de deux objets. En termes d'objets, il existe deux approches différentes qui sont utilisées à différentes étapes du processus de vérification de l'égalité de deux objets. Entrons dans les détails.

Égalité référentielle

Dans le langage de programmation Java, les valeurs réelles des objets sont stockées quelque part dans la mémoire et elles sont pointées par les références d'objet qui sont également stockées quelque part dans la mémoire. Regardons l'extrait de code suivant ;

// Create an object with "new" operator.
// Point to object with object reference "a".
Object a = new Object();

// Create another object reference.
// Point to the same object as the reference "a" is pointing.
Object b = a;

// Output is true.
System.out.println(a == b);

// Create another object with "new" operator.
// Point to object with object reference "b".
b = new Object();

// Output is false.
System.out.println(a == b);

Dans un tel scénario, "a" et "b" sont en fait deux références qui pointent vers le même objet, on peut donc dire directement que "a" est égal à "b" puisque l'égalité référentielle est obtenue.

Cependant, le scénario inverse n'est pas valable tout le temps. Deux références différentes peuvent pointer vers des emplacements de mémoire différents, mais les objets que les références pointent peuvent toujours être égaux.

Dans la vie normale, les quantités sont parfois comparées. Par exemple, si vous avez deux pommes et que votre ami en a deux, le nombre de pommes que vous et votre ami avez est égal.

D'autre part, parfois, les choses peuvent également être comparées en termes de propriétés ou de structure. Entrons dans les détails de ce scénario.

Égalité structurelle

L'égalité structurelle est le type dont les objets sont comparés selon les états de leurs propriétés. Un outil plus sophistiqué est nécessaire pour vérifier ce type d'égalité plutôt que seulement l'opérateur "==". C'est la méthode equals(). Il vient de la classe Object qui est au point le plus élevé de la hiérarchie des classes dans le langage de programmation Java. Vérifions le prochain extrait de code Java ;

public class Car {

    // Assume that we have corresponding constructor, accessors, mutators, etc.

    private String brand;
    private String model;
    private Integer modelYear;
    private Integer mileage;
    
}

Car car1 = new Car("Audi","R8", 2023);
Car car2 = new Car("Audi","R8", 2023);
Car car3 = new Car("Audi","R8", 2021);

System.out.println(car1.equals(car2));
System.out.println(car2.equals(car3));

public boolean equals(Object obj) {
    return (this == obj);
}

public class Car {

    private String brand;
    private String model;
    private Integer modelYear;
    private Integer mileage;

    // Assume that we have corresponding constructor, accessors, mutators, etc.

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Car car = (Car) o; 
        return brand.equals(car.brand) && model.equals(car.model) && modelYear.equals(car.modelYear);
    }
    
}

Sur la deuxième ligne, nous commençons à vérifier l'identité des objets. Si la référence d'objet o est nulle ou si les deux références d'objet pointent vers des objets appartenant à des classes différentes, alors on peut dire directement que ces deux objets n'ont pas la même identité et ne sont pas égaux. Si ce n'est pas le cas, nous sommes enfin en mesure de vérifier les structures réelles des objets.

Sur la troisième ligne de la méthode, nous transtypons la référence o au type de classe correspondant puisque la méthode est remplacée par la classe Object et maintenant nous savons qu'elle appartient à la classe Car.

Enfin, sur la dernière ligne de la méthode, la structure des deux objets est vérifiée et c'est sur la ligne proprement dite que l'identité est définie. Simplement, si les modèles de marques et les années modèles sont les mêmes alors ces deux objets sont égaux. En d'autres termes, c'est l'égalité structurelle.

Nous avons appris les propriétés de l'égalité mathématique à l'école élémentaire. Certaines de ces propriétés doivent également être valides pour la méthode equals () . Cela signifie que ces propriétés doivent être ajoutées aux ingrédients de la méthode equals() . Voyons ces propriétés en termes d'égalité mathématique et en termes d'égalité en Java ;

Propriété réflexive

Dans la définition mathématique de la propriété réflexive, pour tous les nombres réels x ;

x = x

Un nombre est égal à lui-même.

En termes d'égalité Java, un objet doit être égal à lui-même ;

Car car1 = new Car("Audi","R8",2013);
System.out.println(car1.equals(car1));

@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Car car = (Car) o; 
        return mileage < car.mileage && brand.equals(car.brand) && model.equals(car.model) && modelYear.equals(car.modelYear);
    }

Propriété symétrique

En termes de définition mathématique de la propriété symétrique, pour tous les nombres réels x et y ;

Si x = y , alors y = x

L'ordre d'égalité n'a pas d'importance.

En termes d'égalité Java, si l'objet a est égal à l'objet b, alors l'objet b doit être égal à l'objet a ;

Car car1 = new Car("Audi","R8",2013);
Car car2 = new Car("Audi","R8",2013);
System.out.println(car1.equals(car2));
System.out.println(car2.equals(car1));

public class Car {

    private String brand;
    private String model;
    private Integer modelYear;
    private Integer mileage;

    @Override
    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof Car)) return false;
        Car car = (Car) o;
        return brand.equals(car.brand) && model.equals(car.model) && modelYear.equals(car.modelYear);
    }
}

public class SportCar extends Car {
    
    private String engineType;

    @Override
    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof SportCar)) return false;
        SportCar sportCar = (SportCar) o;
        return engineType.equals(sportCar.engineType);
    }
}

Car car1 = new Car("Audi", "R8", 2012, 1000);
SportCar car2 = new SportCar("Audi", "R8", 2012, 1000, "4.0");
System.out.println(car1.equals(car2));
System.out.println(car2.equals(car1));

Propriété transitive

Dans la définition mathématique de la propriété transitive, pour tous les nombres réels x , y et z ;

si x = y et y = z , alors x = z

Deux nombres égaux au même nombre sont égaux entre eux.

En termes d'égalité Java, si l'objet a est égal à l'objet b et l'objet b est égal à l'objet c, alors l'objet a doit être égal à l'objet c ;

SportCar car1 = new SportCar("Audi", "R8", 2012, 1000, new Turbo("100RPM"));
Car car2 = new Car("Audi", "R8", 2012, 1000);
SportCar car3 = new SportCar("Audi", "R8", 2012, 1000, new Turbo("101RPM"));
System.out.println(car1.equals(car2));
System.out.println(car2.equals(car3));
System.out.println(car1.equals(car3));

public class SportCar extends Car {
    
    private Turbo turbo;

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Car)) return false;
        if (!(o instanceof SportCar)) return o.equals(this);
        return super.equals(o) && ((SportCar) o).turbo == turbo;
    }
}

public class Turbo {
    private String power;

    public Turbo(String power) {
        this.power = power;
    }
}

SportCar car1 = new SportCar("Audi", "R8", 2012, 1000, new Turbo("100RPM"));
Car car2 = new Car("Audi", "R8", 2012, 1000);
SportCar car3 = new SportCar("Audi", "R8", 2012, 1000, new Turbo("101RPM"));
System.out.println(car1.equals(car2));
System.out.println(car2.equals(car3));
System.out.println(car1.equals(car3));

Propriété cohérente

Il s'agit d'une propriété différente puisqu'il n'y a pas d'équivalent exact mathématiquement. Pour celui-ci, nous devons obtenir le même résultat pour les exécutions consécutives de la méthode equals ().

Regardons l'extrait de code ci-dessous ;

Car car1 = new Car("Audi", "R8", 2012, 1000);
Car car2 = new Car("Audi", "R8", 2012, 1000);

for (int i = 0; i < 100000; i++) {
    System.out.println(car1.equals(car2));
}

@Override
public boolean equals(Object o) {
    if (o == this) return true;
    if (!(o instanceof Car)) return false;
    Car car = (Car) o;
    Random random = new Random();
    return brand.equals(car.brand) && model.equals(car.model) && (random.nextInt(10) + 2013) == car.modelYear;
}

Remarque : Cet exemple ne me satisfait pas vraiment. Après une recherche approfondie, je vais le remplacer par un meilleur. Il est clairement indiqué dans la documentation Java de la méthode equals () que "les invocations multiples de x.equals(y)reviennent de manière cohérente trueou reviennent de manière cohérente false". Il doit y avoir un exemple solide de violation pour cette déclaration.

Propriété de paramètre nul

C'est ainsi que j'ai choisi d'appeler cette propriété.

Veuillez regarder l'extrait de code ci-dessous ;

Car car1 = new Car("Audi", "R8", 2012, 1000);
Car car2 = null;
System.out.println(car1.equals(car2));

Maintenant, il est clair quelles propriétés une implémentation de la méthode equals() doit avoir afin de fournir un calcul correct de l'égalité. De plus, nous avons vu les scénarios de violation possibles, nous pouvons donc maintenant les éviter dans l'intérêt de la qualité du code et de la logique métier.

Aujourd'hui, de nombreux IDE fournissent des mécanismes de génération pour générer les meilleures implémentations possibles de la méthode equals() . C'est une bonne idée de s'y tenir la plupart du temps au lieu d'implémenter la méthode equals() . Voyons un exemple d' Intellij IDEA ;

@Override
   public boolean equals(Object o) {
       if (this == o) return true;
       if (o == null || getClass() != o.getClass()) return false;
       Car car = (Car) o;
       return brand.equals(car.brand) && model.equals(car.model) && modelYear.equals(car.modelYear) && mileage.equals(car.mileage);
   }

Sur la première ligne, l'égalité référentielle est vérifiée, car deux références d'objet peuvent pointer vers le même objet.

Sur la deuxième ligne, la validation de la propriété Null Parameter est assurée et également la même condition si agit comme une clause de garde if en contrôlant le type de classe des objets qui sont pointés par des références afin d'empêcher la violation de la propriété transitive et de la propriété symétrique .

Sur la troisième ligne, la référence d'objet est transtypée dans le type de classe correspondant qui est Car dans notre exemple afin d'atteindre les propriétés de l'objet Car.

Enfin, sur la quatrième ligne, l'égalité est vérifiée en termes d'égalité structurelle.

Il existe d'autres modèles pour la génération de la méthode equals() . Vous pouvez également aller les vérifier.

Si vous souhaitez écrire l'implémentation par vous-même, gardez à l'esprit que l'implémentation doit garantir la validité de toutes les propriétés de la méthode equals() .

Alors que nous arrivons à la fin de l'article, il reste encore une lacune très importante à couvrir. Ouvrez votre IDE et essayez de générer la méthode equals() . Vous allez voir qu'il n'y a pas d'option pour générer uniquement la méthode equals() sans la méthode hashCode() . En raison de certains types de collections dans le langage de programmation Java, il est recommandé de ne pas séparer ces deux méthodes l'une de l'autre. Il est également dit dans la documentation officielle de Java qu'il est généralement nécessaire de remplacer la méthode hashCode() chaque fois que la méthode equals() est remplacée. Je voudrais donner des informations correspondantes sur le hachage et la méthode hashCode() dans un autre de mes articles.

De plus, il se peut que nous devions parfois ne pas nous en tenir aux propriétés de la méthode equals () pour certains cas exceptionnels, tels que les classes d'entités de JPA. Il mérite aussi son propre article.

En conclusion

Tous les points importants pour implémenter la bonne méthode equals() sont traités dans cet article. Je vous recommande également d'écrire les tests unitaires correspondants pour tester la validation des propriétés de la méthode equals() . N'oubliez pas de regarder les autres ressources qui peuvent être fondées sur le web.

En premier lieu, cela semble être un sujet très simple et primitif puisqu'il fait partie des principes fondamentaux de la conception orientée objet. Cependant, comme on le voit dans les détails, il est très important de ne pas ignorer ces concepts de base et de bien les comprendre.