points de vue

les déambulations d'un codeur

Aller au contenu | Aller au menu | Aller à la recherche

L'égalité d'identité et l'égalité de valeur en Java

Dans certains langages de programmation, comme Java, il n’y a pas de méthodes ou d’opérateurs distincts entre l’égalité d’identité et celle de valeur. Si, dans un programme classique écrit dans un langage comme Java, l’égalité d’identité (de l’OID pour Object IDentifier) pourrait se faire avec l’opérateur == et celle de valeur avec la méthode equals surchargée, il n’en va plus de même dès qu’il s’agit d’objets persistés. Et là, in fine, c’est le drame : que compare t’on avec la méthode equals ? la valeur des objets ou leur identité ?

Cette question est importante car elle met en exergue une différence de sémantique qui, mal appréciée ou pas du tout appréhendée, pourrait aboutir à utiliser tantôt l’une, tantôt l’autre, aboutissant inévitablement à des bogues cachés, voir même, dans certaines situations d’usage, à un plantage.

Cette question est d’autant plus pertinente en Java où la méthode equals, par ses propres règles de codage, peut véhiculer les deux sémantiques. Je m’explique. Par défaut, la méthode equals d’un objet en Java effectue une égalité d’identité (qui se trouve être l’adresse mémoire de l’objet, comme dans les autres langages orientés objet). Nous pourrions par conséquence en déduire que la sémantique véhiculée par cette méthode est celle d’une égalité d’identité et donc nous y maintenir. Or, une des règles de codage en Java stipule que lorsqu’un objet peut être comparé, la méthode equals doit être consistante avec la méthode compareTo ; c’est-à-dire que :

myObject.compareTo(anotherObject) == 0 <=> myObject.equals(anotherObject) == true

Or la méthode compareTo effectue une comparaison de valeur ce qui fait que, par extension, la méthode equals devient alors une égalité de valeur !

A première vue, nous pourrions croire alors qu’il n’y a pas de sémantique bien définie avec la méthode equals. En fait, il en est rien. Sa sémantique est bien celle d’une égalité d’identité et le problème ici relève plus de l’utilisation de la comparaison entre objets. En effet, il est fréquent de rencontrer dans du code une implémentation de la méthode compareTo dès qu’une comparaison entre différents objets d’une même classe est à faire. Par exemple, dans le cas d’un tri. Or, ceci peut n’avoir aucun sens vis à vis du concept véhiculé par ces objets. Prenons un exemple. Imaginons que l’on souhaite trier des événements calendaires par leur date de début. Une des solutions pourrait être d’implémenter l’interface Comparable et sa méthode compareTo pour comparer les dates de début. Or ceci est une erreur conceptuelle ; on désire comparer ici des dates, pas les événements calendaires eux même. En effet, la méthode compareTo est une opération de comparaison de valeurs entre deux objets, autrement dit une opération de comparaison d’états entre deux objets. Or, la date de début d’un événement n’est qu’une caractéristique parmi d’autres de son état. Qu’en est-il de sa date de fin ? De sa récurrence ? De son intitulé ? etc. Comparer deux événements n’a de sens que pour indiquer s’ils sont in fine soit différents, soit égaux en valeur, et ça ce n’est pas la sémantique de la méthode compareTo. Pour comparer les événements par leur date de début, on utilisera plutôt un objet de type Comparator qui transportera lui la sémantique de comparaison que l’on voudra sur les événements et qui portera en fait sur une ou plusieurs de leurs propriétés.

Mais alors, qu’en est-il de l’intérêt de l’interface Comparable et de sa méthode compareTo ? Vous connaissez la classe String ? Cette classe qui représente une chaîne de caractères en Java est immutable et constante. Ceci signifie qu’une même chaîne de caractères est représentée par une même instance et ne peut être modifiée ; nous avons ici une égalité à la fois d’identité et de valeur. Lorsque vous comparez deux variables distinctes avec une même chaîne de caractères, en fait vous comparez derrière le même objet (les variables sont des références qui pointent ici sur la même instance). Si vous modifiez une chaîne de caractères, en fait, vous n’obtenez qu’une autre chaîne de caractères, donc une autre instance avec une autre valeur ; celle sur laquelle a été demandée la modification n’a pas changé. Et par conséquent, la méthode equals de String, donnant une égalité à la fois d’identité et de valeur, est consistante avec la méthode compareTo. C’est dans ce contexte que cette méthode prend tout son sens : lorsque l’identité et la valeur d’un objet sont intrinsèquement liés, autrement dit lorsque vos objets sont immutables.

En conséquence, la méthode equals en Java est une opération d’égalité d’identité et devrait le rester dans vos programmes. Si une égalité par valeur doit se faire entre deux de vos objets avec la méthode equals, alors soit vos objets doivent être immutables et vous pouvez surcharger la méthode equals en ce sens, soit vous devez définir une nouvelle méthode, comme par exemple sameAs.

Maintenant, les choses sont un tant soit peu un peu plus délicats avec les objets persistés. En effet, ce qui est stocké dans une source de données, quelle qu’elle soit (une source NoSQL ou une base de données), c’est l’état des objets, leur valeur, et en aucune manière les objets mêmes. (Ou alors vous disposez d’une source de données orientée objet et, mise à part dans le monde Smalltalk, je n’en connais aucune qui soit utilisée dans les autres écosystèmes de programmation.) Aussi, si par deux fois un même événement calendaire est demandé, le programme obtiendra, sans utilisation de caches, deux instances différentes de ce même événement et construites à partir d’une même valeur récupérée de la source de données. La méthode equals retournera donc, par défaut, false lorsqu’un bout de code voudra savoir s’il s’agit d’un même événement. Dans ce contexte, la sémantique d’égalité d’identité est brisée.

À la différence des objets à vie courte, les objets persistants ont une durée de vie qui va au delà du temps d’exécution du programme, voir même ont une vie au delà d’un processus donné. L’identité par adresse mémoire ne suffit donc plus. C’est là qu’intervient l’identité de persistance ou de stockage. En effet, cette identité assure l’unicité de l’objet dans la source de données et peut donc être utilisée aussi dans le programme pour marquer l’identité des objets en lieu et place de leur adresse mémoire. Pour aller plus loin et assurer aussi son unicité au delà des sources de données, l’idéal serait que cette identité soit une valeur unique et universelle (UUID). Ainsi, dès que vous devez manipuler des objets persistés, vous pouvez surcharger la méthode equals pour qu’elle compare l’égalité des identifiants de persistance des objets si ces derniers sont persistés, sinon leur valeur d’adresse si l’un d’eux ne l’est pas :

public boolean equals(Object other) {
  if (! other instanceof CalendarEvent) {
    return false;
  }
  CalendarEvent otherEvent = (CalendarEvent) other;
  if (this.getId() != null && otherEvent.getId() != null) {
    return this.equals(otherEvent.getId());
  }
  return this == otherEvent;
}

(Nous pouvons aussi s’assurer ici que la méthode getId renvoie toujours une valeur sous forme de Value Object, soit le hash code dans le cas où l’objet n’est pas encore persisté, sinon l’identifiant de persistance.)

Miguel Moquillon

Auteur: Miguel Moquillon

Restez au courant de l'actualité et abonnez-vous au Flux RSS de cette catégorie

Commentaires (0)

Soyez le premier à réagir sur cet article

Ajouter un commentaire Fil des commentaires de ce billet

no attachment



À voir également

L'approche Micro-Services et le paradigme Orienté-Objet

J’aime à dire que l’évolution en informatique ne connait que très peu de ruptures et ressemble à une grande spirale dans laquelle les nouvelles technologies et approches ne sont que des reprises de plus anciennes que l’on a dépoussiéré et adapté aux attentes et au temps actuels ; c’est ce que l’on appelle l’innovation. Les micro-services, le grand buzz de l’année 2015, n’échappent pas à cette régle. (On est friand aussi de buzz en informatique). Mais qu’est-ce que les micro-services ? En fait, rien de spécial. Ce n’est ni plus ni moins que le découpage des responsabilités des applications dans des services Web dédiés et qui communiquent entre eux par messages. Du SOA ? Du REST ? … Oui, tout ça mais ce n’est plus à la mode, il faut parler maintenant de micro-services.

Lire la suite

card slot

Une histoire de slots

L’amélioration de la structuration du code d’un programme, avec la distinction des considérations techniques de celles métier, est le Saint Graal que poursuivent sans fins les développeurs. A cette fin, de nombreuses techniques ont fait leur apparition dont nous pouvons citer les traits ou les annotations. A côté de ceux-ci, il existe une technique élégante et uniforme que sont les slots. Mais, que sont ces derniers et en quoi peuvent ils nous aider dans notre quête ?

Lire la suite