I. Introduction▲
Dans cet article, nous allons nous intéresser aux tâches d'écriture de code dans les beans classiques. Les méthodes qu'on ajoute sont plus ou moins toujours les mêmes (getters, setters, toString, hashCode, etc.) au point que la plupart des développeurs les codent comme un réflexe, le plus souvent à l'aide de quelques clics sous Eclipse. Or c'est souvent du code technique, pas très intéressant, qui pollue le programme et en rend la lecture moins aisée.
Nous allons voir comment des bibliothèques comme Commons Lang, Guava ou Lombok aident à développer du code plus simple.
I-A. À propos▲
Découvrir une technologie n'est pas chose facile. En aborder plusieurs d'un coup l'est encore moins. Partant de ce constat, cet article a été écrit pour aller à l'essentiel. Les points importants sont présentés dans le corps de l'article et les éléments secondaires sont expliqués en annexe.
I-B. Avant de commencer▲
Pour écrire ce tutoriel, j'ai utilisé les éléments suivants :
- Java JDK 1.6.0_24-b07 ;
- Eclipse Indigo 3.7 JEE 64b ;
- Maven 3.0.3 ;
- Commons Lang 3.1 ;
- Guava 11.0.1 ;
- Lombok 0.11.0.
I-C. Copyright sur les images de chien▲
Les images de chien utilisées dans ce tutoriel sont soumises à un copyright : vous ne pouvez pas les copier. Je dispose d'une licence (payante) pour les utiliser, mais pas pour les redistribuer. D'avance, je vous remercie pour votre compréhension.
I-D. Présentation dans des JUG▲
Cet article m'a été utile pour préparer une présentation au LyonJug, LorraineJug, NormandyJug et BrezthJUG. Les slides de ces présentations sur disponibles online : http://icauda.com/cours.html
II. Préparation▲
II-A. Télécharger, installer et importer le projet d'exemple▲
Pour commencer, je vous propose de télécharger le fichier Zip « nice-dog-1.zip » contenant un projet Java-Maven d'exemple qui va nous servir de support pour les fonctionnalités présentées dans la suite de cet article.
Compilez le projet d'exemple et importez-le dans Eclipse (comme expliqué dans le tutoriel « Importer un projet Maven dans Eclipse en 5 minutes ») ou dans l'IDE de votre choix.
Pour suivre ce tutoriel, vous pouvez vous contenter de lire les codes proposés ci-dessous (codes complets en annexe) et de faire confiance aux codes et captures d'écran.
II-B. Découverte projet d'exemple▲
Durée estimée : 15 secondes.
En plus des fichiers de Maven (« pom.xml ») et de licence, le projet ne contient que la classe « Dog » pour représenter naïvement les caractéristiques d'un chien : nom, nom complet, date de naissance, race, inscription au livre des origines (lof), père, mère, couleur et poids. Pour faire bonne mesure, la classe possède également un identifiant dont la valeur peut venir de la base par exemple.
public
class
Dog {
private
Integer id;
private
String name;
private
String fullName;
private
Date birthday;
private
String race;
private
Boolean lof;
private
Dog father;
private
Dog mother;
private
String color;
private
Double weight;
}
Dans la suite de ce tutoriel, nous allons ajouter des fonctionnalités classiques au bean « Dog » : constructeurs, accesseurs, hash code, etc.
III. Action avec Eclipse▲
Traditionnellement, lorsqu'on développe une nouvelle classe avec Eclipse, on écrit d'abord les attributs puis on demande à Eclipse de générer les constructeurs et les accesseurs (getters et setters). Très souvent, on enchaine sur les méthodes classiques (héritées de Object) telles que « toString », « hashCode » et « equals ». Pour de nombreux programmeurs, c'est presque un automatisme. Toutefois, cette procédure est vite laborieuse et produit du code purement technique qui alourdit les sources et pénalise la compréhension.
Cette partie de l'action, à l'aide d'Eclipse, montre ce que font habituellement les développeurs. Dans les chapitres suivants, nous verrons comment simplifier et améliorer ce processus à l'aide de Commons Lang, Guava et Lombok.
III-A. Menu source▲
Les fonctionnalités de génération de code que nous allons utiliser dans la suite sont accessibles à partir du menu « Source » dans la barre des menus en haut de la fenêtre. Ce menu est également utilisable à partir du clic droit dans le code.
III-B. Constructeurs▲
Durée estimée : 2 minutes.
Eclipse permet de générer deux types de constructeurs :
- des constructeurs hérités des éventuelles classes parentes (dont « Object »), avec notamment le constructeur par défaut (sans argument) ;
- des constructeurs utilisant les attributs de la classe comme paramètre.
Pour générer un constructeur hérité, il faut passer par le menu « Sources/Generate Constructors from Superclass… ». Dans l'exemple, il n'y a que le constructeur par défaut qui est proposé. Si les classes parentes avaient défini d'autres constructeurs, ils auraient été également proposés.
Dans la popup, on peut choisir où sera inséré le code généré.
public
class
DogEclipse {
private
Integer id;
private
String name;
private
String fullName;
private
Date birthday;
private
String race;
private
Boolean lof;
private
Dog father;
private
Dog mother;
private
String color;
private
Double weight;
public
DogEclipse
(
) {
super
(
);
// TODO Auto-generated constructor stub
}
}
Pour générer un constructeur avec argument, il faut passer par le menu « Sources/Generate Constructor using Fields… ». La popup permet de sélectionner les attributs à utiliser et de les ordonner. Pour créer plusieurs constructeurs, avec des paramètres et/ou des ordres différents, il suffit de relancer la fonctionnalité autant de fois que nécessaire.
Dans un premier temps, je génère un constructeur qui utilise tous les attributs de la classe, que je laisse dans l'ordre initial.
public
class
DogEclipse {
...
public
DogEclipse
(
Integer id, String name, String fullName, Date birthday, String race, Boolean lof, Dog father, Dog mother, String color, Double weight) {
super
(
);
this
.id =
id;
this
.name =
name;
this
.fullName =
fullName;
this
.birthday =
birthday;
this
.race =
race;
this
.lof =
lof;
this
.father =
father;
this
.mother =
mother;
this
.color =
color;
this
.weight =
weight;
}
}
Dans un second temps, je génère un constructeur n'utilisant que les attributs nom, nom complet, date de naissance et race.
public
class
DogEclipse {
...
public
DogEclipse
(
String name, String fullName, Date birthday, String race) {
super
(
);
this
.name =
name;
this
.fullName =
fullName;
this
.birthday =
birthday;
this
.race =
race;
}
}
On constate que le code généré est relativement simple, et standard.
III-C. Accesseurs▲
Durée estimée : 30 secondes.
La génération des accesseurs (getters et setters) est certainement la fonctionnalité de génération de code la plus utilisée d'Eclipse.
Pour générer les getters et setters, il faut passer par le menu « Sources/Generate Getters and Setters… ». La popup permet de sélectionner les attributs pour lesquels on veut générer les accesseurs.
Dans la popup, on peut préciser la visibilité (private, public, etc.) qu'on souhaite utiliser. Eclipse adapte la liste des choix en fonction des attributs.
public
class
DogEclipse {
...
public
Integer getId
(
) {
return
id;
}
public
void
setId
(
Integer id) {
this
.id =
id;
}
public
String getName
(
) {
return
name;
}
...
}
Ici, on ne peut pas faire beaucoup plus simple et efficace.
En tant que développeur, je ne peux pas m'imaginer en train d'écrire des getters et des setters à la main (c'est-à-dire sans génération), ne serait-ce que pour un seul attribut. C'est impensable…
III-D. Hash code et Equals▲
Durée estimée : 1 minute.
Les méthodes « hashCode » et « equals » permettent respectivement d'avoir une représentation numérique d'un objet et de le comparer avec un autre objet.
Je n'inclus pas le père et la mère dans ces méthodes, car ça reviendrait à naviguer dans l'arbre généalogique, ce qui n'est pas mon but. Et puis c'est sans compter les orphelins et les problèmes récursifs qui entraineraient un (trop) long débat.
public
class
DogEclipse {
...
@Override
public
int
hashCode
(
) {
final
int
prime =
31
;
int
result =
1
;
result =
prime *
result +
((
birthday ==
null
) ? 0
: birthday.hashCode
(
));
result =
prime *
result +
((
color ==
null
) ? 0
: color.hashCode
(
));
result =
prime *
result +
((
fullName ==
null
) ? 0
: fullName.hashCode
(
));
result =
prime *
result +
((
id ==
null
) ? 0
: id.hashCode
(
));
result =
prime *
result +
((
lof ==
null
) ? 0
: lof.hashCode
(
));
result =
prime *
result +
((
name ==
null
) ? 0
: name.hashCode
(
));
result =
prime *
result +
((
race ==
null
) ? 0
: race.hashCode
(
));
result =
prime *
result +
((
weight ==
null
) ? 0
: weight.hashCode
(
));
return
result;
}
public
class
DogEclipse {
...
@Override
public
boolean
equals
(
Object obj) {
if
(
this
==
obj)
return
true
;
if
(
obj ==
null
)
return
false
;
if
(
getClass
(
) !=
obj.getClass
(
))
return
false
;
DogEclipse other =
(
DogEclipse) obj;
if
(
birthday ==
null
) {
if
(
other.birthday !=
null
)
return
false
;
}
else
if
(!
birthday.equals
(
other.birthday))
return
false
;
if
(
color ==
null
) {
if
(
other.color !=
null
)
return
false
;
}
else
if
(!
color.equals
(
other.color))
return
false
;
if
(
fullName ==
null
) {
if
(
other.fullName !=
null
)
return
false
;
}
else
if
(!
fullName.equals
(
other.fullName))
return
false
;
...
return
true
;
}
}
On constate que le code généré (version complète en annexe) est relativement lourd.
III-E. To String▲
Durée estimée : 30 secondes.
La méthode « toString » donne une représentation de l'objet sous forme de caractères. En général, on utilise le nom de la classe et les valeurs de ses attributs intéressants.
public
class
DogEclipse {
...
@Override
public
String toString
(
) {
return
"DogEclipse [id="
+
id +
", name="
+
name
+
", fullName="
+
fullName +
", birthday="
+
birthday
+
", race="
+
race +
", lof="
+
lof +
", color="
+
color
+
", weight="
+
weight +
"]"
;
}
}
III-F. Bilan des générations à l'aide d'Eclipse▲
Sans compter les imports et les commentaires, il aura fallu environ deux cents lignes de code, pas toujours très lisibles, pour équiper la classe « Dog » avec les méthodes classiques (et indispensables).
IV. Action avec Commons Lang▲
Les « Commons » sont un projet Apache qui existe depuis assez longtemps. Dans ce chapitre nous allons en utiliser une sous-partie : « Commons Lang ».
IV-A. Maven▲
Durée estimée : 1 minute.
Pour utiliser Apache Commons Lang, il faut ajouter une dépendance dans le fichier « pom.xml ».
<!-- Commons Lang -->
<dependency>
<groupId>
org.apache.commons</groupId>
<artifactId>
commons-lang3</artifactId>
<version>
3.1</version>
</dependency>
Puis relancer une installation Maven.
mvn clean install eclipse:eclipse
En fonction des éléments déjà présents sur le disque dur, le résultat de l'installation Maven devrait ressembler à la trace suivante :
D:\javadev\article\developpez.com\nice-dog>
mvn clean install eclipse:eclipse
[INFO] Scanning for
projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Nice dog 1
.0
-SNAPSHOT
...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4
.076s
[INFO] Finished at: Fri Jun 08
20
:21
:15
CEST 2012
[INFO] Final Memory: 12M/162M
[INFO] ------------------------------------------------------------------------
On doit ensuite faire un refresh (touche F5) dans Eclipse pour faire apparaitre Guava (et ses dépendances) dans la liste des bibliothèques référencées.
IV-B. Equals et hash code▲
Commons Lang va nous permettre de simplifier sensiblement le code de la méthode « equals » présentée plus haut. Pour cela, la bibliothèque dispose de l'objet « EqualsBuilder » auquel on indique les attributs à prendre en compte :
import
org.apache.commons.lang3.builder.EqualsBuilder;
public
class
DogCommonsLang {
private
Integer id;
private
String name;
private
String fullName;
private
Date birthday;
...
@Override
public
boolean
equals
(
Object obj) {
if
(
this
==
obj)
return
true
;
if
(
obj ==
null
)
return
false
;
if
(!(
obj instanceof
DogCommonsLang))
return
false
;
DogCommonsLang other =
(
DogCommonsLang) obj;
return
new
EqualsBuilder
(
)
.append
(
id, other.id)
.append
(
name, other.name)
.append
(
fullName, other.fullName)
.append
(
birthday, other.birthday)
.append
(
race, other.race)
.append
(
lof, other.lof)
.append
(
color, other.color)
.append
(
weight, other.weight)
.isEquals
(
);
}
}
Même principe pour la méthode « hashCode », on utilise l'objet « HashCodeBuilder » auquel on indique les attributs souhaités :
import
org.apache.commons.lang3.builder.HashCodeBuilder;
public
class
DogCommonsLang {
private
Integer id;
private
String name;
private
String fullName;
private
Date birthday;
private
String race;
...
@Override
public
int
hashCode
(
) {
return
new
HashCodeBuilder
(
17
, 37
)
.append
(
id)
.append
(
name)
.append
(
fullName)
.append
(
birthday)
.append
(
race)
.append
(
lof)
.append
(
color)
.append
(
weight)
.toHashCode
(
);
}
}
Bien entendu, l'ordre des « append » a son importance. Si on change l'ordre, on aura un résultat différent.
IV-C. To String▲
Encore le même principe pour la méthode « toString », on utilise l'objet « ToStringBuilder » auquel on indique les attributs souhaités :
import
org.apache.commons.lang3.builder.ToStringBuilder;
public
class
DogCommonsLang {
private
Integer id;
private
String name;
private
String fullName;
private
Date birthday;
private
String race;
...
@Override
public
String toString
(
) {
return
new
ToStringBuilder
(
this
)
.append
(
"id"
, id)
.append
(
"name"
, name)
.append
(
"fullName"
, fullName)
.append
(
"birthday"
, birthday)
.append
(
"race"
, race)
.append
(
"lof"
, lof)
.append
(
"color"
, color)
.append
(
"weight"
, weight)
.toString
(
);
}
}
IV-D. Comparaison compareTo▲
La méthode « compareTo » est indispensable lorsqu'on a besoin de comparer des beans en Java, par exemple pour trier des listes. À ma connaissance, Eclipse ne propose pas de fonction de génération de code pour cette méthode. Dans la suite, je vous propose donc du code Java classique, tel que j'avais l'habitude de l'écrire avant de découvrir Commons Lang (ou Guava).
Pour qu'un bean soit « comparable », il faut implémenter l'interface « Comparable ».
À titre d'exemple, nous allons limiter les comparaisons aux attributs id, name, fullName, race, color et weight. Ça ne prend pas en compte les attributs birthday, lof, father et mother. En Java classique, cela ressemblerait au code suivant :
public
class
Dog implements
Comparable<
Dog>
{
private
Integer id;
private
String name;
private
String fullName;
private
Date birthday;
private
String race;
private
Boolean lof;
private
Dog father;
private
Dog mother;
private
String color;
private
Double weight;
/**
* Dans l'ordre : id, name, fullName, race, color et weight.
*
* Ça ne prend pas en compte birthday, lof, father et mother.
*/
@Override
public
int
compareTo
(
Dog other) {
int
result =
0
;
result =
id.compareTo
(
other.id);
if
(
result !=
0
) {
return
result;
}
result =
name.compareTo
(
other.name);
if
(
result !=
0
) {
return
result;
}
result =
fullName.compareTo
(
other.fullName);
if
(
result !=
0
) {
return
result;
}
result =
race.compareTo
(
other.race);
if
(
result !=
0
) {
return
result;
}
result =
color.compareTo
(
other.color);
if
(
result !=
0
) {
return
result;
}
result =
weight.compareTo
(
other.weight);
if
(
result !=
0
) {
return
result;
}
return
result;
}
}
Ce code est relativement long et lourd. Heureusement, Comons Lang propose la classe « CompareToBuilder » qui simplifie clairement la méthode.
implements
Comparable<
DogCommonsLang>
{
public
class
DogCommonsLang implements
Comparable<
DogCommonsLang>
{
private
Integer id;
private
String name;
private
String fullName;
private
Date birthday;
private
String race;
...
/**
* Dans l'ordre : id, name, fullName, race, color et weight.
*
* Ça ne prend pas en compte birthday, lof, father et mother.
*/
@Override
public
int
compareTo
(
DogCommonsLang other) {
return
new
CompareToBuilder
(
)
.append
(
id, other.id)
.append
(
name, other.name)
.append
(
fullName, other.fullName)
.append
(
race, other.race)
.append
(
color, other.color)
.append
(
weight, other.weight)
.toComparison
(
);
}
}
IV-E. Bilan de l'utilisation de Commons Lang▲
Comme on l'a vu, l'utilisation de Commons Lang permet de gagner quelques lignes de code, mais ce n'est pas l'essentiel. Ce qui compte, c'est que le code est bien plus lisible et robuste, ce qui en augmente la qualité et la maintenance.
V. Action avec Guava▲
Guava est une bibliothèque créée par Google, sous l'impulsion de Kevin Bourrillon et la supervision de Josh Bloch. Dans cet article, nous allons utiliser uniquement ses composants « base », mais on peut garder en tête que Guava fait beaucoup d'autres choses.
Le nom initial de Guava était « Google-Collections ». J'avais d'ailleurs écrit un article sur les Google CollectionsA la découverte du framework Google Collections il y a quelques mois, avant le changement de nom.
V-A. Maven▲
Durée estimée : 1 minute.
Pour utiliser Guava, il faut ajouter une dépendance dans le fichier « pom.xml ».
<!-- Guava -->
<dependency>
<groupId>
com.google.guava</groupId>
<artifactId>
guava</artifactId>
<version>
11.0.1</version>
</dependency>
Puis relancer une installation Maven.
mvn clean install eclipse:eclipse
En fonction des éléments déjà présents sur le disque dur, le résultat de l'installation Maven devrait ressembler à la trace suivante :
D:\javadev\article\developpez.com\nice-dog>
mvn clean install eclipse:eclipse
[INFO] Scanning for
projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Nice dog 1
.0
-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2
.4
.1
:clean (
default-clean) @ nice-dog ---
[INFO] Deleting D:\javadev\article\developpez.com\nice-dog\target
[INFO]
[INFO] --- maven-resources-plugin:2
.4
.3
:resources (
default-resources) @ nice-dog ---
[INFO] Using 'UTF-8'
encoding to copy filtered resources.
[INFO] Copying 0
resource
[INFO]
[INFO] --- maven-compiler-plugin:2
.3
.1
:compile (
default-compile) @ nice-dog ---
[INFO] Compiling 2
source files to D:\javadev\article\developpez.com\nice-dog\target\classes
[INFO]
[INFO] --- maven-resources-plugin:2
.4
.3
:testResources (
default-testResources) @ nice-dog ---
[INFO] Using 'UTF-8'
encoding to copy filtered resources.
[INFO] Copying 0
resource
[INFO]
[INFO] --- maven-compiler-plugin:2
.3
.1
:testCompile (
default-testCompile) @ nice-dog ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2
.7
.2
:test (
default-test) @ nice-dog ---
[INFO] Surefire report directory: D:\javadev\article\developpez.com\nice-dog\target\surefire-reports
...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4
.076s
[INFO] Finished at: Fri Jun 08
20
:21
:15
CEST 2012
[INFO] Final Memory: 12M/162M
[INFO] ------------------------------------------------------------------------
On doit ensuite faire un refresh (touche F5) dans Eclipse pour faire apparaitre Guava (et ses dépendances) dans la liste des bibliothèques référencées.
V-B. Equals et hash code▲
Durée estimée : 2 minutes.
Guava permet de simplifier la méthode « equals » à l'aide de l'objet « Objects » et plus spécifiquement de sa méthode « equals » :
public
class
DogGuava {
...
@Override
public
boolean
equals
(
Object obj) {
if
(
this
==
obj) return
true
;
if
(
obj ==
null
) return
false
;
if
(!
(
obj instanceof
DogGuava)) return
false
;
DogGuava other =
(
DogGuava) obj;
return
Objects.equal
(
id, other.id)
&&
Objects.equal
(
name, other.name)
&&
Objects.equal
(
fullName, other.fullName)
&&
Objects.equal
(
race, other.race)
&&
Objects.equal
(
lof, other.lof)
&&
Objects.equal
(
color, other.color)
&&
Objects.equal
(
weight, other.weight);
}
On voit que Guava et Commons Lang ont deux approches différentes. Guava propose principalement l'objet « Objects » dont les méthodes réalisent des fonctionnalités variées alors que Commons Lang préfère proposer plusieurs objets pour réaliser les mêmes opérations.
Pour simplifier la méthode « hashCode » à l'aide de Guava, on va là aussi utiliser l'objet « Objects », mais avec « hashCode » :
public
class
DogGuava {
...
@Override
public
int
hashCode
(
) {
return
Objects.hashCode
(
id, name, fullName, birthday, race, lof, color, weight);
}
La version Guava de la méthode « hashCode » est donc un peu plus concise que celle de Commons Lang.
V-C. To String▲
Sans surprise, pour améliorer la méthode « toString » de notre chien avec Guava, on va utiliser les fonctions « toString » et « toStringHelper » de l'objet « Objects » :
public
class
DogGuava {
...
@Override
public
String toString
(
) {
return
Objects.toStringHelper
(
this
)
.add
(
"id"
, id)
.add
(
"name"
, name)
.add
(
"fullName"
, fullName)
.add
(
"birthday"
, birthday)
.add
(
"race"
, race)
.add
(
"lof"
, lof)
.add
(
"color"
, color)
.add
(
"weight"
, weight)
.toString
(
);
}
C'est tout de même plus lisible.
V-D. Comparaison de canins▲
L'API Guava est relativement riche lorsqu'il s'agit de comparer des objets. Dans notre cas, pour réaliser la méthode « compareTo », le plus simple est d'utiliser la classe « ComparisonChain » :
public
class
DogGuava implements
Comparable<
DogGuava>
{
private
Integer id;
private
String name;
private
String fullName;
private
Date birthday;
private
String race;
private
Boolean lof;
private
Dog father;
private
Dog mother;
private
String color;
private
Double weight;
...
/**
* Dans l'ordre : id, name, fullName, race, color et weight.
*
* Ça ne prend pas en compte birthday, lof, father et mother.
*/
@Override
public
int
compareTo
(
DogGuava other) {
return
ComparisonChain.start
(
)
.compare
(
id, other.id)
.compare
(
name, other.name)
.compare
(
fullName, other.fullName)
.compare
(
race, other.race)
.compare
(
color, other.color)
.compare
(
weight, other.weight)
.result
(
);
}
...
Guava s'arrête dès que la comparaison donne un résultat. En d'autres mots, la bibliothèque n'est pas obligée d'aller jusqu'au bout pour renvoyer sa réponse.
On peut passer (en troisième argument de « compare ») un comparateur spécifique. On n'est donc pas limité au comparateur automatique.
V-E. Bilan de l'utilisation de Guava▲
On constate que notre bean est relativement plus simple grâce à Guava. La bibliothèque ne permet pas un gain considérable en nombre de lignes, mais elle rend le code plus lisible, plus efficace et plus maintenable.
On voit qu'il y a finalement assez peu de différences entre les versions de notre chien écrites avec Commons Lang et Guava. Apache avait été un précurseur sur le sujet, mais s'était fait plus discret au passage à Java 5 (en particulier sur les generics). Google, quant à lui, est relativement actif et Guava est un peu plus à la mode que Commons Lang. Alors, lequel choisir ? Les deux bibliothèques remplissent le contrat de façon équivalente. Le choix de l'un ou de l'autre sera une question de goût. Ou de mode… Dans tous les cas, je vous invite à consulter une discussion sur stackoverflow (Guava Vs Apache)Discussion sur stackoverflow (Guava Vs Apache) qui montre qu'il y a un très large consensus en faveur de Guava.
VI. Action avec Lombok▲
Lombok fonctionne directement à l'aide d'annotations (contrairement à Commons Lang et Guava). Comme nous allons le voir, cela permet d'écrire des classes relativement minimalistes.
Dans ce chapitre, je ne vais présenter que les fonctionnalités de Lombok qui sont en rapport avec le fil rouge de cet article. Lombok est plus gros que ça. Je détaille les autres fonctionnalités (annotations) en annexe.
VI-A. Maven▲
Durée estimée : 1 minute.
Pour utiliser Lombok, il faut ajouter une dépendance dans le fichier « pom.xml ».
<!-- Lombok -->
<dependency>
<groupId>
org.projectlombok</groupId>
<artifactId>
lombok</artifactId>
<version>
0.11.0</version>
</dependency>
Puis relancer une installation Maven.
mvn clean install eclipse:eclipse
En fonction des éléments déjà présents sur le disque dur, le résultat de l'installation Maven devrait ressembler à la trace suivante :
D:\javadev\article\developpez.com\nice-dog>
mvn clean install eclipse:eclipse
[INFO] Scanning for
projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Nice dog 1
.0
-SNAPSHOT
[INFO] ------------------------------------------------------------------------
Downloading: http://repository.jboss.org/nexus/content/groups/public/org/projectlombok/lombok/0
.11
.0
/lombok-0
.11
.0
.jar
Downloaded: http://repository.jboss.org/nexus/content/groups/public/org/projectlombok/lombok/0
.11
.0
/lombok-0
.11
.0
.jar (
1707
KB at 206
.8
KB/sec)
[INFO]
[INFO] --- maven-clean-plugin:2
.4
.1
:clean (
default-clean) @ nice-dog ---
[INFO] Deleting D:\javadev\article\developpez.com\nice-dog\target
[INFO]
[INFO] --- maven-resources-plugin:2
.4
.3
:resources (
default-resources) @ nice-dog ---
[INFO] Using 'UTF-8'
encoding to copy filtered resources.
[INFO] Copying 0
resource
[INFO]
[INFO] --- maven-compiler-plugin:2
.3
.1
:compile (
default-compile) @ nice-dog ---
[INFO] Compiling 2
source files to D:\javadev\article\developpez.com\nice-dog\target\classes
[INFO]
[INFO] --- maven-resources-plugin:2
.4
.3
:testResources (
default-testResources) @ nice-dog ---
[INFO] Using 'UTF-8'
encoding to copy filtered resources.
[INFO] Copying 0
resource
[INFO]
[INFO] --- maven-compiler-plugin:2
.3
.1
:testCompile (
default-testCompile) @ nice-dog ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2
.7
.2
:test (
default-test) @ nice-dog ---
[INFO] Surefire report directory: D:\javadev\article\developpez.com\nice-dog\target\surefire-reports
...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4
.076s
[INFO] Finished at: Fri Jun 08
21
:31
:23
CEST 2012
[INFO] Final Memory: 11M/162M
[INFO] ------------------------------------------------------------------------
On doit ensuite faire un refresh (touche F5) dans Eclipse pour faire apparaitre Lombok (et ses dépendances) dans la liste des bibliothèques référencées.
VI-B. Installer Lombok dans Eclipse▲
Durée estimée : 1 minute.
Puisque Lombok fonctionne à base d'annotations, les méthodes générées n'apparaissent pas dans la fenêtre « outline » d'Eclipse. Pour y remédier, il faut installer un petit plugin dans l'IDE.
Pour ajouter les fonctionnalités de Lombok à l'IDE Eclipse, il faut d'abord télécharger le « jar » de Lombok sur le site Web du projet LombokSite Web du projet Lombok (cf. capture en annexe, rubrique « liens »), puis double-cliquer (sous Windows) sur le fichier « jar » pour l'exécuter.
Je conseille de quitter Eclipse avant de poursuivre l'installation.
Dans la fenêtre qui s'ouvre, il faut sélectionner le dossier d'installation de l'IDE (Eclipse et Netbeans sont supportés) à l'aide du bouton « specify location ».
Puis cliquer sur le bouton « Install/Update ».
Et pour finir, il suffit de quitter et relancer Eclipse.
Il n'est nécessaire d'exécuter cette procédure qu'une seule fois, pour l'ensemble de tous les projets.
VI-C. Constructeurs▲
Commençons par le commencement : les constructeurs. Grâce à Lombok, on peut les remplacer en partie par l'ajout de simples annotations.
À titre de rappel, voici à quoi ressemble Eclipse avant qu'on commence à utiliser Lombok. Au fur et à mesure, on va voir que la fenêtre « outline » va se remplir.
import
lombok.NoArgsConstructor;
import
lombok.AllArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
public
class
DogLombok {
private
Integer id;
private
String name;
private
String fullName;
private
Date birthday;
private
String race;
private
Boolean lof;
private
Dog father;
private
Dog mother;
private
String color;
private
Double weight;
}
On constate que les constructeurs sont apparus dans la fenêtre « outline » (et donc aussi dans le code compilé) alors qu'ils sont absents du code Java pur.
Quand on décompile le résultat des annotations « @NoArgsConstructor » et « @AllArgsConstructor », on obtient le code suivant.
import
java.beans.ConstructorProperties;
import
java.util.Date;
public
class
DogLombok
{
private
Integer id;
private
String name;
private
String fullName;
...
public
DogLombok
(
)
{
}
@ConstructorProperties
({
"id"
, "name"
, "fullName"
, "birthday"
, "race"
, "lof"
, "father"
, "mother"
, "color"
, "weight"
}
)
public
DogLombok
(
Integer id, String name, String fullName, Date birthday, String race, Boolean lof, Dog father,
Dog mother, String color, Double weight)
{
this
.id =
id;
this
.name =
name;
this
.fullName =
fullName;
this
.birthday =
birthday;
...
}
}
Sans surprise, ça ressemble à du code tel qu'on a l'habitude de l'écrire, avec le bonus du « @ConstructorProperties », mais il n'y a pas grand-chose à inventer sur ce sujet.Quoique…
Avec Lombok, on peut également créer un constructeur n'utilisant qu'une partie des attributs à l'aide de l'annotation « @RequiredArgsConstructor », mais il y a une petite mécanique à comprendre.
import
lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public
class
DogLombok {
private
Integer id;
private
String name;
private
String fullName;
private
Date birthday;
...
}
Cette annotation va générer un « static factory » qui prend en paramètre tous les attributs marqués « final » et les attributs annotés « @NonNull ». Bien entendu, il est possible d'indiquer à l'annotation « @RequiredArgsConstructor » le nom de la méthode à créer.
import
lombok.RequiredArgsConstructor;
import
lombok.NonNull;
@RequiredArgsConstructor
(
staticName =
"of"
)
public
class
DogLombok {
@NonNull
private
Integer id;
@NonNull
private
String name;
private
String fullName;
private
Date birthday;
...
}
L'exemple ci-dessus crée donc un constructeur « static » prenant les attributs « id » et « name » en paramètres. Pour l'utiliser, on écrira un code ressemblant au suivant :
DogLombok dog=
DogLombok.of
(
12
, "Milou"
);
Pour encore mieux comprendre comment ça fonctionne, voici le code décompilé correspondant à cet exemple.
import
lombok.NonNull;
public
class
DogLombok
{
@NonNull
private
Integer id;
@NonNull
private
String name;
private
String fullName;
private
Date birthday;
...
private
DogLombok
(
@NonNull
Integer id, @NonNull
String name)
{
if
(
id ==
null
)
throw
new
NullPointerException
(
"id"
);
if
(
name ==
null
)
throw
new
NullPointerException
(
"name"
);
this
.id =
id;
this
.name =
name;
}
public
static
DogLombok of
(
@NonNull
Integer id, @NonNull
String name)
{
return
new
DogLombok
(
id, name);
}
}
Il y a encore un peu de travail à fournir sur cet aspect pour que la bibliothèque soit parfaite, car on ne peut créer qu'un seul « constructeur static », avec un seul jeu de paramètres qui, de surcroit, sont associés à des attributs devant répondre à des conditions contraignantes. Toutefois, d'expérience, cela correspond à des situations qu'on retrouve relativement souvent et peut donc s'apparenter à un compromis acceptable. À voir ce que nous proposeront les prochaines versions de Lombok…
VI-D. Getters et setters▲
Les accesseurs correspondent certainement à une des cibles privilégiées des utilisateurs de Lombok. En effet, les getters et setters standards ne sont que du code technique, qui prend une place considérable dans les beans. Dit autrement, il faut six lignes de code pour écrire les accesseurs d'un seul attribut. Comme on va le voir, Lombok permet d'économiser 75 % d'espace au minimum.
Pour créer un getter à l'aide de Lombok, il faut utiliser l'annotation « @Getter » sur l'attribut concerné. Pour créer un setter, on doit l'annoter avec « @Setter » :
import
lombok.Getter;
import
lombok.Setter;
@NoArgsConstructor
@AllArgsConstructor
public
class
DogLombok {
@Getter
@Setter
private
Integer id;
...
C'est magique. On voit que les méthodes apparaissent dans la fenêtre « outline » d'Eclipse.
Faisons de même avec l'ensemble des attributs. Sans compter les commentaires et les lignes de séparation, les accesseurs des dix attributs nécessitaient soixante lignes de code (80 avec les sauts de ligne et environ 150 en comptant la Javadoc standard). Grâce à Lombok, on gagne cinquante lignes (140 en comptant les sauts de ligne et la doc).
import
lombok.Getter;
import
lombok.Setter;
@NoArgsConstructor
@AllArgsConstructor
public
class
DogLombok {
@Getter
@Setter
private
Integer id;
@Getter
@Setter
private
String name;
@Getter
@Setter
private
String fullName;
@Getter
@Setter
private
Date birthday;
@Getter
@Setter
private
String race;
...
Clairement, on gagne un nombre de lignes énorme, mais on peut encore en gagner plus dans le cas où on veut générer des accesseurs pour tous les attributs. Dans ce cas, on peut directement ajouter les annotations au niveau de la classe.
import
lombok.Setter;
@Setter
public
class
DogLombok {
private
Integer id;
private
String name;
private
String fullName;
private
Date birthday;
private
String race;
...
En complément des annotations « @Getter » et « @Setter » au niveau de la classe, qui créent des accesseurs pour tous les attributs, on peut utiliser ces annotations en complément sur chaque attribut pour changer les valeurs par défaut.
Le code suivant génère ainsi des setters « public » pour tous les attributs (à cause de l'annotation « @Setter » au niveau de la classe) sauf pour les attributs « id » (pour lequel on ne génère rien) et « name » (pour lequel on génère un setter « protected »).
import
static
lombok.AccessLevel.NONE;
import
static
lombok.AccessLevel.PROTECTED;
import
lombok.Getter;
@Setter
public
class
DogLombok {
@Setter
(
NONE)
private
Integer id;
@Setter
(
PROTECTED)
private
String name;
private
String fullName;
private
Date birthday;
private
String race;
...
VI-D-1. getter lazy▲
Un peu en marge des « getters » classiques, imaginons que le bean comporte un attribut « private » et « final », ce qui n'est pas si rare, et que la valeur de l'attribut soit couteuse à calculer. Le code d'un tel bean ressemblerait alors au bloc suivant.
@Getter
public
class
DogLombokAvecLazzy {
private
Integer id;
private
String name;
private
String fullName;
...
private
final
String description =
calculdescription
(
); // constante locale
private
String calculdescription
(
) {
System.out.println
(
"calculdescription"
);
return
"foo"
+
Math.random
(
);
}
}
Dans l'exemple, la valeur de l'attribut « description » est calculée dès la création du bean. Java ne peut pas savoir à l'avance si l'attribut sera accédé et donc si le calcul ne sera pas réalisé pour rien.
Pour vérifier cela, aidons-nous de quelques traces :
System.out.println
(
"avant creation"
);
DogLombokAvecLazzy dog =
new
DogLombokAvecLazzy
(
);
System.out.println
(
"avant appel"
);
dog.getDescription
(
);
System.out.println
(
"apres appel"
);
avant creation
calculdescription
avant appel
apres appel
Ce qu'on aimerait, c'est que la valeur soit « lazy calculated », c'est-à-dire qu'elle n'est calculée que lorsqu'on en a besoin. Avec Lombok, il suffit de le préciser dans l'annotation « @Getter » à l'aide du paramètre « lazy ».
@Getter
public
class
DogLombokAvecLazzy {
...
@Getter
(
lazy =
true
)
private
final
String description =
calculdescription
(
); // constante locale
private
String calculdescription
(
) {
System.out.println
(
"calculdescription"
);
return
"foo"
+
Math.random
(
);
}
}
avant creation
avant appel
calculdescription
apres appel
On constate que la valeur n'est calculée que lorsqu'on la demande. Pour réaliser la même chose en Java pur, il faut de nombreuses lignes de code complexe.
VI-E. Hash code et Equals▲
Pour créer les méthodes « equals » et « hashCode », il faut utiliser l'annotation « @EqualsAndHashCode » (qui génère les deux d'un coup) au niveau de la classe :
import
lombok.EqualsAndHashCode;
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public
class
DogLombok {
...
}
L'annotation « @EqualsAndHashCode » utilise tous les attributs non « static » pour remplir les contrats des méthodes « equals » et « hashCode ». Si on souhaite exclure une partie des attributs, il faut utiliser le paramètre « exclude » de l'annotation. Par exemple, pour exclure les attributs « father » et « mother », on peut écrire le code suivant :
@EqualsAndHashCode
(
exclude =
{
"father"
, "mother"
}
)
public
class
DogLombok {
private
Integer id;
private
String name;
private
String fullName;
private
Date birthday;
private
String race;
private
Boolean lof;
private
Dog father;
private
Dog mother;
private
String color;
private
Double weight;
}
Si, comme moi, on préfère spécifier la liste des attributs à prendre en compte, on doit utiliser le paramètre « of » de l'annotation « @EqualsAndHashCode ». Par exemple, pour utiliser les attributs « name », « fullName » et « id », on peut écrire le code suivant :
@EqualsAndHashCode
(
of =
{
"name"
, "fullName"
, "id"
}
)
public
class
DogLombok {
private
Integer id;
private
String name;
private
String fullName;
private
Date birthday;
private
String race;
...
}
VI-F. To String▲
Pour générer la méthode « toString » à l'aide de Lombok, il suffit d'utiliser l'annotation « @ToString » au niveau de la classe :
import
lombok.ToString;
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@ToString
public
class
DogLombok {
...
}
DogLombok
(
id
=
123
, name
=
Milou, fullName
=
Milou de Belgique, birthday
=
somedate, race
=
fox terrier, lof
=
true, father
=
null, mother
=
null, color
=
blanc, weight
=
10
.5
)
Par défaut, l'annotation « @ToString » utilise tous les attributs non marqués « static » pour remplir le contrat de la méthode « toString ». Si on souhaite exclure certains attributs, on peut utiliser le paramètre « exclude » de l'annotation.
@ToString
(
exclude={
"father"
, "mother"
}
)
public
class
DogLombok {
private
Integer id;
private
String name;
private
String fullName;
private
Date birthday;
private
String race;
private
Boolean lof;
private
Dog father;
private
Dog mother;
private
String color;
private
Double weight;
}
DogLombok
(
id
=
123
, name
=
Milou, fullName
=
Milou de Belgique, birthday
=
somedate, race
=
fox terrier, lof
=
true, color
=
blanc, weight
=
10
.5
)
À l'opposé, on peut préférer indiquer la liste des attributs qu'on souhaite utiliser. Dans ce cas, il faut se servir du paramètre « of » de l'annotation « @ToString ».
@ToString
(
of={
"id"
, "name"
, "fullName})
public
class
DogLombok {
private
Integer id;
private
String name;
private
String fullName;
private
Date birthday;
...
}
DogLombok
(
id
=
123
, name
=
Milou, fullName
=
Milou de Belgique)
Je préfère indiquer les attributs à utiliser (paramètre « of ») que ceux à exclure (paramètre « exclude »), car je trouve que c'est plus lisible. Cela dit, c'est une affaire de goût.
Si on ne souhaite pas un rappel du nom des attributs, on peut utiliser le paramètre « includeFieldNames » de l'annotation « @ToString ».
@ToString
(
of =
{
"id"
, "name"
, "fullName"
}
, includeFieldNames =
false
)
public
class
DogLombok {
private
Integer id;
private
String name;
private
String fullName;
....
}
DogLombok
(
123
, Milou, Milou de Belgique)
Je trouve que ça rend le résultat moins lisible. Je n'utilise donc jamais le paramètre « includeFieldNames ».
VI-G. Tout ça à la fois▲
Si on veut faire, d'un seul coup, tout ce qui a été présenté dans les paragraphes précédents, on peut utiliser l'annotation « @Data » au niveau de la classe :
L'annotation « @Data » fonctionne comme l'addition des annotations « @Getter », « @Setter », « @ToString », « @EqualsAndHashCode » et « @RequiredArgsConstructor ».
import
lombok.Data;
@Data
public
class
DogLombok {
private
Integer id;
private
String name;
private
String fullName;
private
Date birthday;
...
}
Pour le coup, on peut difficilement imaginer plus concis.
La version décompilée de cet exemple, utilisant uniquement l'annotation « @Data », est disponible en annexe.
VI-H. Bilan de l'utilisation de Lombok▲
Sans compter les imports et les commentaires, il aura fallu seulement une douzaine de lignes de code pour avoir la classe complète. Sur ces lignes, une dizaine sont incompressibles puisqu'elles définissent la classe et ses attributs. Avec la seule annotation « @Data » de Lombok, on arrive donc au même résultat qu'avec les 190 lignes de code purement techniques générées par Eclipse ; cela se ressent sur la lisibilité et sur la maintenance.
Le projet Lombok-pgLombok-pg est un bon complément à Lombok, auquel il ajoute une vingtaine d'annotations.
VII. Conclusion▲
Comme on a pu le constater dans cet article, Apache Commons Lang et Google Guava apportent des solutions relativement semblables au surpoids du bean « Dog ». À l'opposé, Lombok se base sur une série d'annotations dont une partie (« @Getter », « @Setter », etc.) aurait une place légitime dans Java.
Ni Guava ni Lombok n'apportent de solution complète aux problématiques exposées dans cet article. Chaque bibliothèque apporte son lot de bons et/ou mauvais côtés. Je propose d'ailleurs une liste personnelle des avantages et des inconvénients en annexe.
Le code final de ce tutoriel est disponible dans le fichier ZIP le fichier Zip « nice-dog-2.zip »
Vos retours nous aident à améliorer nos publications. N'hésitez donc pas à commenter cet article sur le forum : 37 commentaires
Retrouvez mes autres tutoriels sur developpez.com à l'adresse https://thierry-leriche-dessirier.developpez.com/#page_articlesTutoriels
VIII. Remerciements▲
Je tiens à remercier, en tant qu'auteur du tutoriel, toutes les personnes qui m'ont aidé et soutenu. Je pense tout d'abord à mes collègues qui subissent mes questions au quotidien, mais aussi à mes contacts et amis du Web, dans le domaine de l'informatique ou non, qui m'ont fait part de leurs remarques et critiques. Bien entendu, je n'oublie pas l'équipe de developpez.com qui m'a guidé dans la rédaction de cet article et m'a aidé à le corriger et le faire évoluer, principalement sur le forum.
Plus particulièrement j'adresse mes remerciements à Mickael Baron (keulkeul), Nemek, Kevin Bourrillion, Florent Ramière et Claude Leloup.
IX. Annexes▲
IX-A. Liens▲
Commons Lang : http://commons.apache.org/lang/Commons Lang
Guava : http://code.google.com/p/guava-libraries/Guava
Blog Guava by Google : https://blog.developpez.com/guava/
Article « À la découverte du framework Google Collections » (Guava) : https://thierry-leriche-dessirier.developpez.com/tutoriels/java/tuto-google-collections/A la découverte du framework Google Collections
Lombok : http://projectlombok.org/Projet Lombok
Le lien vers le fichier « jar » d'installation de Lombok dans Eclipse se trouve en haut à droite sur le site Web du projet Lombok (cf. capture).
Lombok-pg : https://github.com/peichhorn/lombok-pg/wikiLombok-pg
IX-B. Liens personnels▲
Retrouvez ma page et mes autres articles sur developpez.com à l'adresse https://thierry-leriche-dessirier.developpez.com/#page_articlesTutoriels
Ajoutez-moi à vos contacts à l'aide du QR Code suivant :
Suivez-moi sur Twitter (@thierrylerichehttps://twitter.com/#!/thierryleriche@thierryleriche) :
IX-C. Comparatif▲
Voici un récapitulatif des différentes solutions :
Commons Lang |
Guava |
Lombok |
|
---|---|---|---|
Constructeurs |
- |
- |
« @NoArgsConstructor », « @AllArgsConstructor » et « @RequiredArgsConstructor » |
Accesseurs |
- |
- |
- |
Equals et hashCode |
« EqualsBuilder » et « HashCodeBuilder » |
« Objects.equal » et « Objects.hashCode » |
« @EqualsAndHashCode » |
ToString |
« ToStringBuilder » |
« Objects.toStringHelper » |
« @ToString » |
compareTo |
« CompareToBuilder » |
« ComparisonChain » |
- |
Et voici un tableau détaillant les gains attendus (pour un objet possédant dix attributs) sans compter les lignes vides et les commentaires, en comparaison de la version Eclipse/Java :
Eclipse/Java |
Commons Lang |
Guava |
Lombok |
|
---|---|---|---|---|
Constructeur vide |
3 lignes |
- |
- |
1 ligne (gain de 33 %) |
Constructeur plein |
12 lignes |
1 ligne (gain de 91 %) |
||
Accesseurs |
6 lignes |
- |
- |
1 ligne par attribut (gain de 83 %) |
Equals et hashCode |
12 + 49 lignes |
34 lignes (gain de 45 %) |
34 lignes |
1 ligne (gain de 98 %) |
ToString |
13 lignes |
14 lignes (perte) |
14 lignes |
1 ligne (gain de 92 %) |
compareTo |
34 lignes |
14 lignes (gain de 58 %) |
14 lignes |
- |
IX-D. Avantages et inconvénients▲
Très clairement, ni Commons Lang ni Guava ni Lombok ne couvrent toutes les problématiques que les développeurs rencontrent habituellement dans le cadre de l'écriture des codes standards (beans simples). Les approches des trois bibliothèques (commons Lang/Guava Vs Lombok) sont relativement différentes. Parfois elles se chevauchent, couvrant ainsi des besoins similaires. Parfois, elles se complètent. Parfois, elles ne traitent pas du tout du sujet. Parfois encore, elles sont en opposition.
En ce qui me concerne, je pense qu'on peut utiliser Guava et Lombok de concert. J'aime utiliser Lombok sur les beans simples, pour lesquels je peux me contenter de traitements standardisés comme une méthode « equals » basée sur l'ensemble des attributs. En général, j'utilise carrément l'annotation « @Data » de Lombok. Quand j'ai besoin de préciser un peu plus les choses, ou de réaliser des traitements spécifiques, je fais appel à Guava.
La plupart du temps, j'utilise les annotations « @Getter » et « @Setter » de Lombok au niveau de la classe, car je n'ai rien trouvé de plus concis et efficace.
Finalement, et quand Guava ne suffit plus, il ne me reste que l'option du code Java pur. D'ailleurs, en ce qui concerne les constructeurs (et mis à part pour les beans vraiment simples sur lesquels je ne souhaite pas dépenser mon énergie), je préfère souvent écrire du « vrai » code Java. Je trouve (et ça n'engage que moi) que c'est plus clair et sans ambiguïté au premier coup d'œil.
En fait, je crois que la raison principale de la faible adoption de Lombok est que cette bibliothèque fait des choses trop extraordinaires ; ça peut faire peur.
Dans les avantages de Lombok, je note évidemment la réduction de la quantité de code. On gagne de nombreuses lignes qui correspondent souvent à du code technique sans autre intérêt que d'être indispensable. Je pense en particulier aux accesseurs (getters et setters) qui demandent souvent de « scroller » pour savoir s'ils sont présents. Avec les annotations dédiées, on le sait dès la déclaration des attributs.
En outre, encore plus que pour savoir s'ils sont présents, on doit souvent scroller pour s'assurer que les méthodes classiques ne réalisent pas des choses exotiques ou inhabituelles. Dans la suite, je vais prendre l'exemple d'un setter, mais le raisonnement s'applique aussi aux getters, equals, hashCode, etc.
Un setter classique/standard se contente d'affecter un attribut :
public
class
MaClasse {
private
String field;
...
public
void
setField
(
String field) {
this
.field =
field;
}
...
Mais, parfois, les setters font des trucs inhabituels, par exemple :
public
class
MaClasse {
// private String field;
private
Map<
String, Integer>
maMap;
...
public
void
setField
(
String field) {
Integer value =
maMap.get
(
field);
if
(
value ==
null
) {
value =
0
;
}
maMap.put
(
field, ++
value);
someDao.save
(
maMap);
...
}
...
Juste en regardant la fenêtre outline, il n'y a aucune chance que je détecte une telle méthode. Il faut absolument scroller pour le découvrir. Et bien entendu, je ne peux pas me permettre de scroller dans tous les beans de mes programmes à chaque fois que je veux utiliser une fonction. Et bien entendu, je pars du principe que la Javadoc va (volontairement) oublier de préciser qu'il y a quelque chose de spécial.
Avec Lombok, c'est plus simple. D'abord, tout est défini par annotations dès le début de la classe. Ensuite, tout est généré et donc je peux m'attendre à toujours avoir du code standard et sans (mauvaise) surprise.
Un peu dans la même idée, on essaie généralement d'avoir des méthodes « equals » et « hashCode » qui soient cohérentes entre elles, c'est-à-dire qui sont construites sur un modèle équivalent. Ce n'est d'ailleurs pas une surprise que Lombok dispose d'une seule annotation (« @EqualsAndHashCode ») pour générer les deux méthodes d'un coup.
Voici ce qu'en dit la JavadocJavadoc de hashCode et equals :
public int hashCode() : [..] The general contract of hashCode is:
- Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.
- If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
- It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hashtables.
public boolean equals(Object obj) : [..] Note that it is generally necessary to override the hashCode method whenever this method is overridden, so as to maintain the general contract for the hashCode method, which states that equal objects must have equal hash codes.
Ce qui arrive souvent (et qui m'est arrivé par inattention lors de l'écriture de cet article), c'est d'avoir les méthodes « equals » est « hashCode » qui ne sont pas cohérentes, par exemple en oubliant de prendre en compte un des attributs. Or, c'est impossible de le détecter sans scroller et regarder attentivement.
Je note également que Lombok, tout comme Guava, est relativement bien développée et que je ne serais pas capable de faire mieux à l'aide de code personnel la plupart du temps. Là je pense en particulier à la synchronisation, au « lazy » et au « future » qui traumatisent de nombreux développeurs.
Pour autant, je pense que Lombok est loin d'être arrivé à maturité, notamment lorsqu'on doit se concentrer sur un sous-ensemble d'attributs, ou des combinaisons variées. C'est un des points qui me gênent plus particulièrement pour les constructeurs.
Un inconvénient que je note, et qui m'a été soufflé par un certain Kevin, concerne la documentation. Avec Lombok, il n'y a aucune documentation qui est générée, notamment par des annotations simples comme « @Getter » par exemple. Et en effet, ça peut vite devenir gênant. En fait, pour vraiment bien faire, il faudrait que ce soit directement intégré à Java et que les annotations utilisent (reportent) la documentation des attributs pour construire celles des méthodes générées. Mais ça n'est pas au programme des futures versions de Java.
IX-E. Annotations Lombok▲
Au moment où j'écris cet article (juin 2012), Lombok possède officiellement les annotations suivantes :
Dans la liste suivante, les annotations marquées d'un astérisque (*) sont expliquées dans le corps de l'article. Les autres sont un peu plus détaillés plus bas.
- « @Getter* » et « @Setter* » : ces deux annotations créent des accesseurs. Par défaut, Lombok crée des getters/setters « public », mais il est possible d'indiquer un autre niveau, à choisir parmi « PUBLIC », « PROTECTED », « PACKAGE » et « PRIVATE ». Ainsi « @Setter(AccessLevel.PROTECTED) private String name; » crée le setter « protected », nommé « setName » pour l'attribut « name ». À noter également qu'il est possible d'empêcher la création d'un accesseur particulier, notamment en association les annotations « @Data », « @Getter » et « @Setter » au niveau de la classe (qui créent des accesseurs pour tous les attributs). Pour cela on doit utiliser le niveau supplémentaire « NONE ».
En outre, il est possible de fabriquer un getter « lazy » sur des attributs marqués « final ». Pour cela, il faut utiliser le paramètre « lazy » de l'annotation « @Getter », comme expliqué plus haut dans cet article ; - « @ToString* » : cette annotation génère la méthode « toString ». Par défaut, elle utilise tous les attributs non marqués « static ». Les paramètres « exclude » et « of » servent respectivement à indiquer si on veut exclure ou utiliser des attributs dans le résultat. Le paramètre « includeFieldNames » sert à préciser si on veut rappeler le nom des attributs utilisés ;
- « @EqualsAndHashCode* » : cette annotation génère les méthodes « equals » et « hasCode » d'un seul coup. Par défaut, elle utilise tous les attributs non marqués « static » ou « transient ». Les paramètres « exclude » et « of » servent respectivement à indiquer si on veut exclure ou utiliser des attributs dans le calcul.
- « @NoArgsConstructor* », « @RequiredArgsConstructor* » et « @AllArgsConstructor* » : ces trois annotations génèrent respectivement un constructeur sans argument, un constructeur utilisant les attributs marqués « final » et/ou « @NonNull », et enfin un constructeur utilisant tous les attributs de la classe. Le paramètre « staticName » de l'annotation « @RequiredArgsConstructor » permet de générer un « static factory » ayant le nom indiqué ;
- « @Data* » : cette annotation peut être vue comme la combinaison de « @ToString », « @EqualsAndHashCode », « @Getter » sur tous les attributs, « @Setter » sur tous les attributs non marqués « final », et « @RequiredArgsConstructor » ;
- « @Log » : cette annotation ajoute un logeur (par exemple Log4J) dans la classe ;
- « @Synchronized » : cette annotation génère l'équivalent d'un « locked synchronize » ;
- « @Cleanup » : cette annotation s'utilise dans le code pour indiquer que Lombok doit gérer la bonne fermeture d'une ressource ;
- « @SneakyThrows » : cette annotation permet de lancer des exceptions sans les déclarer dans la clause "throws de la méthode contenante ;
- « @Delegate » : cette annotation génère les « delegates » de toutes les méthodes « public » du type de l'objet de l'attribut.
Dans la suite, je vous propose quelques explications supplémentaires sur ces annotations. Vous trouverez encore des explications plus précises des annotations LombokDes explications plus précises sur le site du projet.
Et si vous avez besoin d'annotations additionnellesListe des annotations de Lombok-pg, vous pouvez jeter un œil du côté de Lombok-pgLombok-pg, qui en propose une vingtaine : « @Action », « @AutoGenMethodStub », « @DoPrivileged », « @FluentSetter », « @ListenerSupport », « @Singleton », etc.
IX-E-1. Annotation @Log▲
Quand on écrit des programmes, et plus particulièrement des gros programmes, on doit loger un grand nombre d'informations. Pour cela, on doit commencer par déclarer un logeur (par exemple Log4J) dans la classe. En général, on le fait de la manière suivante :
import
org.slf4j.Logger;
import
org.slf4j.LoggerFactory;
public
class
MaClasse {
private
static
final
Logger log =
LoggerFactory.getLogger
(
MaClasse.class
);
public
void
foo
(
) {
log.info
(
"bla bla bla"
);
...
}
...
Bien entendu, quand on veut changer d'implémentation, il faut changer toutes les classes du programme. Et puis ce n'est pas très pratique. Avec Lombok, il suffit d'utiliser l'annotation « @Log » :
import
lombok.extern.java.Log;
@Log
public
class
MaClasse {
public
void
foo
(
) {
log.info
(
"bla bla bla"
);
...
}
...
Si on préfère, on peut directement utiliser les implémentations spécifiques d'Apache, Java, Log4J ou encore Slf4J à l'aide des annotations « @CommonsLog », « @Log », « @Log4j » et « @Slf4j ».
IX-E-2. Annotation @Synchronized▲
Le besoin de synchroniser des blocs de code est assez récurrent. En Java, on utilise principalement deux stratégies : le mot-clé « synchronized » et le « lock synchronized » :
public
class
MaClasse {
public
synchronized
void
foo
(
) {
...
}
...
Le cas d'utilisation qui me vient tout de suite à l'esprit est le singleton :
public
class
MaClasse {
private
static
MaClasse instance;
private
MaClasse
(
) {
}
public
synchronized
static
MaClasse getInstance
(
) {
if
(
instance ==
null
) {
instance =
new
MaClasse
(
);
}
return
instance;
}
...
Le « synchronized lock » réalise un lock sur une variable plutôt que sur la méthode :
public
class
MaClasse {
private
final
Object monObjetLock =
new
Object
(
);
public
void
foo
(
) {
synchronized
(
monObjetLock) {
...
}
}
...
Et là, je ne parle même pas du « synchronized lock double check », qu'on retrouve dans le code de Guava.
Lombok permet de générer un code de « synchronized lock » à l'aide de l'annotation « @Synchronized » :
public
class
MaClasse {
@Synchronized
public
void
foo
(
) {
...
}
...
Si on souhaite réaliser le « synchronized lock » sur une variable de la classe, il suffit d'en indiquer le nom dans l'annotation :
public
class
MaClasse {
private
final
Object monObjetLock =
new
Object
(
);
@Synchronized
(
"monObjetLock"
)
public
void
foo
(
) {
...
}
...
Cela va générer un code équivalent au code proposé plus haut.
IX-E-3. Annotation @Cleanup▲
Avec l'annotation « @Cleanup », on se rapproche de la nouvelle gestion des ressources qui est apparue dans Java 7. Plus concrètement, voici un exemple qui devrait vous rappeler des souvenirs :
public
void
lireJava6
(
String from) {
InputStream in =
null
;
try
{
in =
new
FileInputStream
(
from);
byte
[] b =
new
byte
[10000
];
while
(
true
) {
int
r =
in.read
(
b);
if
(
r ==
-
1
)
break
;
...
}
}
catch
(
Exception e) {
...
}
finally
{
if
(
in !=
null
) {
try
{
in.close
(
);
}
catch
(
Exception e) {
...
}
}
}
}
Ça vous rappelle des souvenirs ou des cauchemars ? Heureusement, Java 7 apporte une réponse. Mais Lombok apporte aussi une solution, dès les versions précédentes du langage :
public
void
lire
(
String from) throws
IOException {
@Cleanup
InputStream in =
new
FileInputStream
(
from);
byte
[] b =
new
byte
[10000
];
while
(
true
) {
int
r =
in.read
(
b);
if
(
r ==
-
1
)
break
;
...
}
}
Le gain saute aux yeux. Bien entendu, toutes les ressources ne disposent pas forcément de la méthode « close ». Dans ce cas, il suffit de spécifier le nom de la méthode équivalente dans l'annotation. Par exemple, si la méthode qu'on veut appeler à la fin du traitement s'appelle « die », le code ressemblera au suivant :
public
void
foo
(
) {
@Cleanup
(
"die"
)
MonObj monObj =
new
MonObj
(
...);
//...
}
Pour faire simple, Lombok va appeler « monObj.die() » à la fin du traitement (c.-à-d. à l'accolade fermante de la méthode).
IX-E-4. Annotation @Delegate▲
Illustrons le fonctionnement de cette annotation en créant un objet « Diet » qui possède plusieurs méthodes « public »
public
class
Diet {
public
void
eatLess
(
) {
// ...
}
public
void
eatApple
(
int
numberOfApples) {
// ...
}
public
double
calcul
(
) {
// ...
return
0
;
}
...
Et disons que nous déclarons un attribut de type « Diet » dans notre « Dog » :
public
class
DogByThierry {
private
Integer id;
private
String name;
private
String fullName;
...
@Delegate
private
Diet diet =
new
Diet
(
);
...
L'annotation « @Delegate » va créer dans « DogByThierry » toutes les méthodes nécessaires pour déléguer des appels vers les méthodes « public » de l'objet « Diet ».
Plus concrètement, voici à quoi va ressembler le code décompilé :
public
class
DogByThierry
{
private
Integer id;
private
String name;
...
private
Diet diet;
...
public
double
calcul
(
)
{
return
getDiet
(
).calcul
(
);
}
public
void
eatApple
(
int
numberOfApples)
{
getDiet
(
).eatApple
(
numberOfApples);
}
public
void
eatLess
(
)
{
getDiet
(
).eatLess
(
);
}
...
IX-E-5. Mot-clé bonus : val▲
En plus de ces annotations, Lombok propose le mot-clé « val » qui fonctionne un peu comme la syntaxe « Diamond » de Java 7 et dont voici un exemple d'utilisation.
List<
String>
list1 =
new
ArrayList<>(
);
La syntaxe « Diamand » n'est disponible qu'à partir de la version 7 de Java.
import
lombok.val;
...
val list2 =
new
ArrayList<
String>(
);
list2.size
(
);
Pour ma part, je préfère la vision de Guava, qui est plus proche de la syntaxe « Diamand ».
import
com.google.common.collect.Lists;
...
List<
String>
list3 =
Lists.newArrayList
(
);
IX-F. Les fichiers importants en entier▲
package
com.dvp.tutoriel.nicedog;
import
java.util.Date;
public
class
Dog implements
Comparable<
Dog>
{
private
Integer id;
private
String name;
private
String fullName;
private
Date birthday;
private
String race;
private
Boolean lof;
private
Dog father;
private
Dog mother;
private
String color;
private
Double weight;
/**
* Dans l'ordre : id, name, fullName, race, color et weight.
*
* Ça ne prend pas en compte birthday, lof, father et mother.
*/
@Override
public
int
compareTo
(
Dog other) {
int
result =
0
;
result =
id.compareTo
(
other.id);
if
(
result !=
0
) {
return
result;
}
result =
name.compareTo
(
other.name);
if
(
result !=
0
) {
return
result;
}
result =
fullName.compareTo
(
other.fullName);
if
(
result !=
0
) {
return
result;
}
result =
race.compareTo
(
other.race);
if
(
result !=
0
) {
return
result;
}
result =
color.compareTo
(
other.color);
if
(
result !=
0
) {
return
result;
}
result =
weight.compareTo
(
other.weight);
if
(
result !=
0
) {
return
result;
}
return
result;
}
}
package
com.dvp.tutoriel.nicedog;
import
java.util.Date;
public
class
DogEclipse {
private
Integer id;
private
String name;
private
String fullName;
private
Date birthday;
private
String race;
private
Boolean lof;
private
Dog father;
private
Dog mother;
private
String color;
private
Double weight;
public
DogEclipse
(
) {
super
(
);
// TODO Auto-generated constructor stub
}
public
DogEclipse
(
Integer id, String name, String fullName, Date birthday, String race, Boolean lof, Dog father, Dog mother, String color, Double weight) {
super
(
);
this
.id =
id;
this
.name =
name;
this
.fullName =
fullName;
this
.birthday =
birthday;
this
.race =
race;
this
.lof =
lof;
this
.father =
father;
this
.mother =
mother;
this
.color =
color;
this
.weight =
weight;
}
public
DogEclipse
(
String name, String fullName, Date birthday, String race) {
super
(
);
this
.name =
name;
this
.fullName =
fullName;
this
.birthday =
birthday;
this
.race =
race;
}
@Override
public
int
hashCode
(
) {
final
int
prime =
31
;
int
result =
1
;
result =
prime *
result +
((
birthday ==
null
) ? 0
: birthday.hashCode
(
));
result =
prime *
result +
((
color ==
null
) ? 0
: color.hashCode
(
));
result =
prime *
result +
((
fullName ==
null
) ? 0
: fullName.hashCode
(
));
result =
prime *
result +
((
id ==
null
) ? 0
: id.hashCode
(
));
result =
prime *
result +
((
lof ==
null
) ? 0
: lof.hashCode
(
));
result =
prime *
result +
((
name ==
null
) ? 0
: name.hashCode
(
));
result =
prime *
result +
((
race ==
null
) ? 0
: race.hashCode
(
));
result =
prime *
result +
((
weight ==
null
) ? 0
: weight.hashCode
(
));
return
result;
}
@Override
public
boolean
equals
(
Object obj) {
if
(
this
==
obj)
return
true
;
if
(
obj ==
null
)
return
false
;
if
(
getClass
(
) !=
obj.getClass
(
))
return
false
;
DogEclipse other =
(
DogEclipse) obj;
if
(
birthday ==
null
) {
if
(
other.birthday !=
null
)
return
false
;
}
else
if
(!
birthday.equals
(
other.birthday))
return
false
;
if
(
color ==
null
) {
if
(
other.color !=
null
)
return
false
;
}
else
if
(!
color.equals
(
other.color))
return
false
;
if
(
fullName ==
null
) {
if
(
other.fullName !=
null
)
return
false
;
}
else
if
(!
fullName.equals
(
other.fullName))
return
false
;
if
(
id ==
null
) {
if
(
other.id !=
null
)
return
false
;
}
else
if
(!
id.equals
(
other.id))
return
false
;
if
(
lof ==
null
) {
if
(
other.lof !=
null
)
return
false
;
}
else
if
(!
lof.equals
(
other.lof))
return
false
;
if
(
name ==
null
) {
if
(
other.name !=
null
)
return
false
;
}
else
if
(!
name.equals
(
other.name))
return
false
;
if
(
race ==
null
) {
if
(
other.race !=
null
)
return
false
;
}
else
if
(!
race.equals
(
other.race))
return
false
;
if
(
weight ==
null
) {
if
(
other.weight !=
null
)
return
false
;
}
else
if
(!
weight.equals
(
other.weight))
return
false
;
return
true
;
}
@Override
public
String toString
(
) {
return
"DogEclipse [id="
+
id +
", name="
+
name
+
", fullName="
+
fullName +
", birthday="
+
birthday
+
", race="
+
race +
", lof="
+
lof +
", color="
+
color
+
", weight="
+
weight +
"]"
;
}
public
Integer getId
(
) {
return
id;
}
public
void
setId
(
Integer id) {
this
.id =
id;
}
public
String getName
(
) {
return
name;
}
public
void
setName
(
String name) {
this
.name =
name;
}
public
String getFullName
(
) {
return
fullName;
}
public
void
setFullName
(
String fullName) {
this
.fullName =
fullName;
}
public
Date getBirthday
(
) {
return
birthday;
}
public
void
setBirthday
(
Date birthday) {
this
.birthday =
birthday;
}
public
String getRace
(
) {
return
race;
}
public
void
setRace
(
String race) {
this
.race =
race;
}
public
Boolean getLof
(
) {
return
lof;
}
public
void
setLof
(
Boolean lof) {
this
.lof =
lof;
}
public
Dog getFather
(
) {
return
father;
}
public
void
setFather
(
Dog father) {
this
.father =
father;
}
public
Dog getMother
(
) {
return
mother;
}
public
void
setMother
(
Dog mother) {
this
.mother =
mother;
}
public
String getColor
(
) {
return
color;
}
public
void
setColor
(
String color) {
this
.color =
color;
}
public
Double getWeight
(
) {
return
weight;
}
public
void
setWeight
(
Double weight) {
this
.weight =
weight;
}
}
package
com.dvp.tutoriel.nicedog;
import
java.util.Date;
import
org.apache.commons.lang3.builder.CompareToBuilder;
import
org.apache.commons.lang3.builder.EqualsBuilder;
import
org.apache.commons.lang3.builder.HashCodeBuilder;
import
org.apache.commons.lang3.builder.ToStringBuilder;
public
class
DogCommonsLang implements
Comparable<
DogCommonsLang>
{
private
Integer id;
private
String name;
private
String fullName;
private
Date birthday;
private
String race;
private
Boolean lof;
private
DogCommonsLang father;
private
DogCommonsLang mother;
private
String color;
private
Double weight;
@Override
public
int
hashCode
(
) {
return
new
HashCodeBuilder
(
17
, 37
)
.append
(
id)
.append
(
name)
.append
(
fullName)
.append
(
birthday)
.append
(
race)
.append
(
lof)
.append
(
color)
.append
(
weight)
.toHashCode
(
);
}
@Override
public
boolean
equals
(
Object obj) {
if
(
this
==
obj)
return
true
;
if
(
obj ==
null
)
return
false
;
if
(!(
obj instanceof
DogCommonsLang))
return
false
;
DogCommonsLang other =
(
DogCommonsLang) obj;
return
new
EqualsBuilder
(
)
.append
(
id, other.id)
.append
(
name, other.name)
.append
(
fullName, other.fullName)
.append
(
birthday, other.birthday)
.append
(
race, other.race)
.append
(
lof, other.lof)
.append
(
color, other.color)
.append
(
weight, other.weight)
.isEquals
(
);
}
@Override
public
String toString
(
) {
return
new
ToStringBuilder
(
this
)
.append
(
"id"
, id)
.append
(
"name"
, name)
.append
(
"fullName"
, fullName)
.append
(
"birthday"
, birthday)
.append
(
"race"
, race)
.append
(
"lof"
, lof)
.append
(
"color"
, color)
.append
(
"weight"
, weight)
.toString
(
);
}
/**
* Dans l'ordre : id, name, fullName, race, color et weight.
*
* Ça ne prend pas en compte birthday, lof, father et mother.
*/
@Override
public
int
compareTo
(
DogCommonsLang other) {
return
new
CompareToBuilder
(
)
.append
(
id, other.id)
.append
(
name, other.name)
.append
(
fullName, other.fullName)
.append
(
race, other.race)
.append
(
color, other.color)
.append
(
weight, other.weight)
.toComparison
(
);
}
}
package
com.dvp.tutoriel.nicedog;
import
java.util.Date;
import
com.google.common.base.Objects;
import
com.google.common.collect.ComparisonChain;
public
class
DogGuava implements
Comparable<
DogGuava>
{
private
Integer id;
private
String name;
private
String fullName;
private
Date birthday;
private
String race;
private
Boolean lof;
private
Dog father;
private
Dog mother;
private
String color;
private
Double weight;
@Override
public
int
hashCode
(
) {
return
Objects.hashCode
(
id, name, fullName, birthday, race, lof, color, weight);
}
@Override
public
boolean
equals
(
Object obj) {
if
(
this
==
obj)
return
true
;
if
(
obj ==
null
)
return
false
;
if
(!
(
obj instanceof
DogGuava))
return
false
;
DogGuava other =
(
DogGuava) obj;
return
Objects.equal
(
id, other.id)
&&
Objects.equal
(
name, other.name)
&&
Objects.equal
(
fullName, other.fullName)
&&
Objects.equal
(
race, other.race)
&&
Objects.equal
(
lof, other.lof)
&&
Objects.equal
(
color, other.color)
&&
Objects.equal
(
weight, other.weight);
}
@Override
public
String toString
(
) {
return
Objects.toStringHelper
(
this
)
.add
(
"id"
, id)
.add
(
"name"
, name)
.add
(
"fullName"
, fullName)
.add
(
"birthday"
, birthday)
.add
(
"race"
, race)
.add
(
"lof"
, lof)
.add
(
"color"
, color)
.add
(
"weight"
, weight)
.toString
(
);
}
/**
* Dans l'ordre : id, name, fullName, race, color et weight.
*
* Ça ne prend pas en compte birthday, lof, father et mother.
*/
@Override
public
int
compareTo
(
DogGuava other) {
return
ComparisonChain.start
(
)
.compare
(
id, other.id)
.compare
(
name, other.name)
.compare
(
fullName, other.fullName)
.compare
(
race, other.race)
.compare
(
color, other.color)
.compare
(
weight, other.weight)
.result
(
);
}
}
package
com.dvp.tutoriel.nicedog;
import
java.util.Date;
import
lombok.Data;
@Data
public
class
DogLombok {
private
Integer id;
private
String name;
private
String fullName;
private
Date birthday;
private
String race;
private
Boolean lof;
private
Dog father;
private
Dog mother;
private
String color;
private
Double weight;
}
Voici une proposition (très personnelle) correspondant au type de bean que j'aime bien écrire. Cela n'engage que moi et je n'invite personne à me suivre. Toutefois je trouve que c'est un bon compromis parce qu'il n'y a pas trop de code purement technique grâce à Lombok et que j'ai pu compléter mes choix à l'aide de Guava :
- Lombok crée le constructeur vide (« @NoArgsConstructor ») et le constructeur plein (« @AllArgsConstructor ») ;
- j'ai ajouté deux constructeurs (en Java classique) avec des attributs sélectionnés pour correspondre à mon domaine ;
- Lombok crée les accesseurs (« @Getter » et « @Setter ») pour tous les attributs ;
- Lombok crée la méthode « toString » (« @ToString ») pour les attributs « id », « name », « fullName », « birthday », « race », « lof », « color » et « weight » ;
- Lombok crée les méthodes « equals » et « hashCode » (« @EqualsAndHashCode ») pour les attributs « name », « fullName » et « id » ;
- Guava me sert pour écrire la méthode « compareTo » pour les attributs « id », « name », « fullName », « race », « color » et « weight ».
Je n'ai pas utilisé l'annotation « @Data », car elle va en contradiction avec mon besoin de précision. Néanmoins, pour un bean plus simple, et dans une approche plus mécanique, j'aurais eu tendance à l'utiliser pour me faciliter la vie.
package
com.dvp.tutoriel.nicedog;
import
java.util.Date;
import
lombok.AllArgsConstructor;
import
lombok.Getter;
import
lombok.NoArgsConstructor;
import
lombok.Setter;
import
com.google.common.base.Objects;
import
com.google.common.collect.ComparisonChain;
/*
* Constructeurs vide (par defaut) et plein (avec tous les attributs).
*/
@NoArgsConstructor
@AllArgsConstructor
/*
* Getter et setter sur tous les attributs.
*/
@Getter
@Setter
/*
* Je prefere indiquer les attributs qui m'interessent.
*/
@ToString
(
of =
{
"id"
, "name"
, "fullName"
, "birthday"
, "race"
, "lof"
, "color"
, "weight"
}
)
/*
* La aussi, je prefere indiquer les attributs qui m'interessent.
*/
@EqualsAndHashCode
(
of =
{
"name"
, "fullName"
, "id"
}
)
public
class
DogByThierry implements
Comparable<
DogByThierry>
{
private
Integer id;
private
String name;
private
String fullName;
private
Date birthday;
private
String race;
private
Boolean lof;
private
Dog father;
private
Dog mother;
private
String color;
private
Double weight;
@Delegate
private
Diet diet =
new
Diet
(
);
/*
* Selon les gouts, on pourra ecrire un vrai constructeur vide ou
* l'annotation @NoArgsConstructor. Ici j'ai mis en commentaire le
* constructeur au profit de l'annotation.
*/
// public DogNice() {
// super();
// }
/*
* Constructeur avec juste l'identite du chien.
*/
public
DogByThierry
(
String name, String fullName, Date birthday, String race) {
this
(
);
this
.name =
name;
this
.fullName =
fullName;
this
.birthday =
birthday;
this
.race =
race;
}
/*
* Constructeur avec l'identite du chien et ses couleurs et poids.
*/
public
DogByThierry
(
String name, String fullName, Date birthday, String race, String color, Double weight) {
this
(
name, fullName, birthday, race);
this
.color =
color;
this
.weight =
weight;
}
/*
* Guava.
*/
/**
* Dans l'ordre : id, name, fullName, race, color et weight.
*
* Ça ne prend pas en compte birthday, lof, father et mother.
*/
@Override
public
int
compareTo
(
DogByThierry other) {
return
ComparisonChain.start
(
)
.compare
(
id, other.id)
.compare
(
name, other.name)
.compare
(
fullName, other.fullName)
.compare
(
race, other.race)
.compare
(
color, other.color)
.compare
(
weight, other.weight)
.result
(
);
}
}
IX-G. Classes décompilées▲
package
com.dvp.tutoriel.nicedog;
import
java.util.Date;
public
class
DogLombok
{
private
Integer id;
private
String name;
private
String fullName;
private
Date birthday;
private
String race;
private
Boolean lof;
private
Dog father;
private
Dog mother;
private
String color;
private
Double weight;
public
Integer getId
(
)
{
return
this
.id;
}
public
String getName
(
)
{
return
this
.name;
}
public
String getFullName
(
)
{
return
this
.fullName;
}
public
Date getBirthday
(
)
{
return
this
.birthday;
}
public
String getRace
(
)
{
return
this
.race;
}
public
Boolean getLof
(
)
{
return
this
.lof;
}
public
Dog getFather
(
)
{
return
this
.father;
}
public
Dog getMother
(
)
{
return
this
.mother;
}
public
String getColor
(
)
{
return
this
.color;
}
public
Double getWeight
(
)
{
return
this
.weight;
}
public
void
setId
(
Integer id)
{
this
.id =
id;
}
public
void
setName
(
String name)
{
this
.name =
name;
}
public
void
setFullName
(
String fullName)
{
this
.fullName =
fullName;
}
public
void
setBirthday
(
Date birthday)
{
this
.birthday =
birthday;
}
public
void
setRace
(
String race)
{
this
.race =
race;
}
public
void
setLof
(
Boolean lof)
{
this
.lof =
lof;
}
public
void
setFather
(
Dog father)
{
this
.father =
father;
}
public
void
setMother
(
Dog mother)
{
this
.mother =
mother;
}
public
void
setColor
(
String color)
{
this
.color =
color;
}
public
void
setWeight
(
Double weight)
{
this
.weight =
weight;
}
public
boolean
equals
(
Object o)
{
if
(
o ==
this
)
return
true
;
if
(!(
o instanceof
DogLombok))
return
false
;
DogLombok other =
(
DogLombok)o;
if
(!
other.canEqual
(
this
))
return
false
;
if
(
getId
(
) ==
null
? other.getId
(
) !=
null
: !
getId
(
).equals
(
other.getId
(
)))
return
false
;
if
(
getName
(
) ==
null
? other.getName
(
) !=
null
: !
getName
(
).equals
(
other.getName
(
)))
return
false
;
if
(
getFullName
(
) ==
null
? other.getFullName
(
) !=
null
: !
getFullName
(
).equals
(
other.getFullName
(
)))
return
false
;
if
(
getBirthday
(
) ==
null
? other.getBirthday
(
) !=
null
: !
getBirthday
(
).equals
(
other.getBirthday
(
)))
return
false
;
if
(
getRace
(
) ==
null
? other.getRace
(
) !=
null
: !
getRace
(
).equals
(
other.getRace
(
)))
return
false
;
if
(
getLof
(
) ==
null
? other.getLof
(
) !=
null
: !
getLof
(
).equals
(
other.getLof
(
)))
return
false
;
if
(
getFather
(
) ==
null
? other.getFather
(
) !=
null
: !
getFather
(
).equals
(
other.getFather
(
)))
return
false
;
if
(
getMother
(
) ==
null
? other.getMother
(
) !=
null
: !
getMother
(
).equals
(
other.getMother
(
)))
return
false
;
if
(
getColor
(
) ==
null
? other.getColor
(
) !=
null
: !
getColor
(
).equals
(
other.getColor
(
)))
return
false
;
return
getWeight
(
) ==
null
? other.getWeight
(
) ==
null
: getWeight
(
).equals
(
other.getWeight
(
));
}
public
boolean
canEqual
(
Object other)
{
return
other instanceof
DogLombok;
}
public
int
hashCode
(
)
{
int
PRIME =
31
;
int
result =
1
;
result =
result *
31
+
(
getId
(
) ==
null
? 0
: getId
(
).hashCode
(
));
result =
result *
31
+
(
getName
(
) ==
null
? 0
: getName
(
).hashCode
(
));
result =
result *
31
+
(
getFullName
(
) ==
null
? 0
: getFullName
(
).hashCode
(
));
result =
result *
31
+
(
getBirthday
(
) ==
null
? 0
: getBirthday
(
).hashCode
(
));
result =
result *
31
+
(
getRace
(
) ==
null
? 0
: getRace
(
).hashCode
(
));
result =
result *
31
+
(
getLof
(
) ==
null
? 0
: getLof
(
).hashCode
(
));
result =
result *
31
+
(
getFather
(
) ==
null
? 0
: getFather
(
).hashCode
(
));
result =
result *
31
+
(
getMother
(
) ==
null
? 0
: getMother
(
).hashCode
(
));
result =
result *
31
+
(
getColor
(
) ==
null
? 0
: getColor
(
).hashCode
(
));
result =
result *
31
+
(
getWeight
(
) ==
null
? 0
: getWeight
(
).hashCode
(
));
return
result;
}
public
String toString
(
)
{
return
"DogLombok(id="
+
getId
(
) +
", name="
+
getName
(
) +
", fullName="
+
getFullName
(
)
+
", birthday="
+
getBirthday
(
) +
", race="
+
getRace
(
) +
", lof="
+
getLof
(
) +
", father="
+
getFather
(
)
+
", mother="
+
getMother
(
) +
", color="
+
getColor
(
) +
", weight="
+
getWeight
(
) +
")"
;
}
}
package
com.dvp.tutoriel.nicedog;
import
com.google.common.collect.ComparisonChain;
import
java.beans.ConstructorProperties;
import
java.util.Date;
public
class
DogByThierry
implements
Comparable<
DogByThierry>
{
private
Integer id;
private
String name;
private
String fullName;
private
Date birthday;
private
String race;
private
Boolean lof;
private
Dog father;
private
Dog mother;
private
String color;
private
Double weight;
public
DogByThierry
(
String name, String fullName, Date birthday, String race)
{
this
(
);
this
.name =
name;
this
.fullName =
fullName;
this
.birthday =
birthday;
this
.race =
race;
}
public
DogByThierry
(
String name, String fullName, Date birthday, String race, String color, Double weight)
{
this
(
name, fullName, birthday, race);
this
.color =
color;
this
.weight =
weight;
}
public
int
compareTo
(
DogByThierry other)
{
return
ComparisonChain.start
(
).compare
(
this
.id, other.id).compare
(
this
.name, other.name)
.compare
(
this
.fullName, other.fullName).compare
(
this
.race, other.race).compare
(
this
.color, other.color)
.compare
(
this
.weight, other.weight).result
(
);
}
public
DogByThierry
(
)
{
}
@ConstructorProperties
({
"id"
, "name"
, "fullName"
, "birthday"
, "race"
, "lof"
, "father"
, "mother"
, "color"
, "weight"
}
)
public
DogByThierry
(
Integer id, String name, String fullName, Date birthday, String race, Boolean lof,
Dog father, Dog mother, String color, Double weight)
{
this
.id =
id;
this
.name =
name;
this
.fullName =
fullName;
this
.birthday =
birthday;
this
.race =
race;
this
.lof =
lof;
this
.father =
father;
this
.mother =
mother;
this
.color =
color;
this
.weight =
weight;
}
public
Integer getId
(
)
{
return
this
.id;
}
public
String getName
(
)
{
return
this
.name;
}
public
String getFullName
(
)
{
return
this
.fullName;
}
public
Date getBirthday
(
)
{
return
this
.birthday;
}
public
String getRace
(
)
{
return
this
.race;
}
public
Boolean getLof
(
)
{
return
this
.lof;
}
public
Dog getFather
(
)
{
return
this
.father;
}
public
Dog getMother
(
)
{
return
this
.mother;
}
public
String getColor
(
)
{
return
this
.color;
}
public
Double getWeight
(
)
{
return
this
.weight;
}
public
void
setId
(
Integer id)
{
this
.id =
id;
}
public
void
setName
(
String name)
{
this
.name =
name;
}
public
void
setFullName
(
String fullName)
{
this
.fullName =
fullName;
}
public
void
setBirthday
(
Date birthday)
{
this
.birthday =
birthday;
}
public
void
setRace
(
String race)
{
this
.race =
race;
}
public
void
setLof
(
Boolean lof)
{
this
.lof =
lof;
}
public
void
setFather
(
Dog father)
{
this
.father =
father;
}
public
void
setMother
(
Dog mother)
{
this
.mother =
mother;
}
public
void
setColor
(
String color)
{
this
.color =
color;
}
public
void
setWeight
(
Double weight)
{
this
.weight =
weight;
}
public
String toString
(
)
{
return
"DogByThierry(id="
+
getId
(
) +
", name="
+
getName
(
) +
", fullName="
+
getFullName
(
)
+
", birthday="
+
getBirthday
(
) +
", race="
+
getRace
(
) +
", lof="
+
getLof
(
) +
", color="
+
getColor
(
)
+
", weight="
+
getWeight
(
) +
")"
;
}
public
boolean
equals
(
Object o)
{
if
(
o ==
this
)
return
true
;
if
(!(
o instanceof
DogByThierry))
return
false
;
DogByThierry other =
(
DogByThierry)o;
if
(!
other.canEqual
(
this
))
return
false
;
if
(
getId
(
) ==
null
? other.getId
(
) !=
null
: !
getId
(
).equals
(
other.getId
(
)))
return
false
;
if
(
getName
(
) ==
null
? other.getName
(
) !=
null
: !
getName
(
).equals
(
other.getName
(
)))
return
false
;
return
getFullName
(
) ==
null
? other.getFullName
(
) ==
null
: getFullName
(
).equals
(
other.getFullName
(
));
}
public
boolean
canEqual
(
Object other)
{
return
other instanceof
DogByThierry;
}
public
int
hashCode
(
)
{
int
PRIME =
31
;
int
result =
1
;
result =
result *
31
+
(
getId
(
) ==
null
? 0
: getId
(
).hashCode
(
));
result =
result *
31
+
(
getName
(
) ==
null
? 0
: getName
(
).hashCode
(
));
result =
result *
31
+
(
getFullName
(
) ==
null
? 0
: getFullName
(
).hashCode
(
));
return
result;
}
}