février
2010
En jettant un oeil aux nouveautées du JDK7, j’étais tombé sur la classe java.util.Object
s
qui proposait quelques méthodes utilitaires concernant les opérations de base des objets.
On y retrouve surtout des méthodes permettant de gérer les valeurs null. A première vue il n’y a rien d’extraordinaire là dedans, et je dois dire que je ne m’y étais pas vraiment attardé…
Mais aujourd’hui je suis tombé sur un billet de Joe Darcy présentant ces méthodes un peu plus en détail.
Et mine de rien cela s’annonce bien plus pratique que ce que je ne l’avais soupçonné…
Comme je le disais, on y retrouve surtout des méthodes permettant de gérer les valeurs nulles sans provoquer d’exception. Par exemple :
static boolean equals(Object a, Object b)
pour comparer deux objets en gérant proprement les cas où les objets seraientnull
.static boolean deepEquals(Object a, Object b)
permet de faire la même chose, mais traitera les tableaux récursivement.static <T> int compare(T a, T b, Comparator<? super T> c)
suit la même logique, mais en utilisant une instance deComparator
.static int hashCode(Object o)
retournera simplement lehashCode()
de l’objet, ou0
s’il estnull
.static String toString(Object o)
pour appelertoString()
sur un objet, en retournant « null » si l’objet estnull
.static String toString(Object o, String nullDefault)
pour appelertoString()
sur un objet, en retournantnullDefault
si l’objet estnull
Donc comme je le disais, à première vue il n’y a rien d’extraordinaire.
Il s’agit pourtant de petit bout de code qu’on est amené à répéter assez souvent, et même si le code derrière tout cela n’est pas bien compliqué, les risques d’erreurs sont nombreux.
Mais surtout cela simplifiera grandement le code !
L’exemple de equals()
Reprennons par exemple ce bout de code de la FAQ Java : Pourquoi et comment redéfinir la méthode equals() ?
@Override
public boolean equals(Object obj) {
// Vérification de l'égalité des références
if (obj == this) {
return true;
}
// Vérification du type du paramètre
if (obj instanceof TestClass) {
// Vérification des valeurs des attributs
TestClass other = (TestClass) obj;
// Pour les attributs de type primitif
// on compare directement les valeurs :
if (this.attribut1 != other.attribut1) {
return false; // les attributs sont différents
}
// Pour les attributs de type objets
// on compare dans un premier temps les références
if (this.attribut2 != other.attribut2) {
// Si les références ne sont pas identiques
// on doit en plus utiliser equals()
if (this.attribut2 == null
|| !this.attribut2.equals(other.attribut2)) {
return false; // les attributs sont différents
}
}
// Si on arrive ici c'est que tous les attributs sont égaux :
return true;
}
return false;
}
}
Ce bout de code vérifie l’égalité des objets en se basant uniquement sur deux attributs.
Bien sûr on peut raccourcir le code, par exemple en utilisant l’opérateur ternaire ? :
mais le résultat n’en est pas forcément plus lisible :
@Override
public boolean equals(Object obj) {
// Vérification de l'égalité des références
if (obj == this) {
return true;
}
// Vérification du type du paramètre
if (obj instanceof TestClass) {
// Vérification des valeurs des attributs
TestClass other = (TestClass) obj;
return ( (this.attribut1 == other.attribut1)
&& ( (this.attribut2 == other.attribut2) || (this.attribut2 != null && this.attribut2.equals(other.attribut2)) ) );
}
return false;
}
Et il ne s’agit que d’une comparaison de deux attributs…
Avec Java 7, la méthode Objects.equals()
nous permettra de faire ceci, ce qui est bien plus clair et précis à mon avis :
@Override
public boolean equals(Object obj) {
// Vérification de l'égalité des références
if (obj == this) {
return true;
}
// Vérification du type du paramètre
if (obj instanceof TestClass) {
// Vérification des valeurs des attributs
TestClass other = (TestClass) obj;
return Objects.equals(this.attribut1, other.attribut1)
&& Objects.equals(this.attribut2, other.attribut2);
}
return false;
}
En clair, toutes ces méthodes nous éviterons d’avoir à gérer des valeurs nulles et nous rendrons donc le code bien plus lisible et bien plus sûr…
Mais ce n’est pas tout, il reste encore deux petites méthodes tout aussi pratiques et insoupçonnables.
Générer un hashCode en tout simplicité
La première est Objects.hash(Object...)
. Elle prend en paramètre un nombre variable d’objet et génèrera une valeur de hashage de cet ensemble, qui pourra par exemple être utilisé pour implémenter la méthode hashCode()
.
Petit retour dans la FAQ : Pourquoi et comment redéfinir la méthode hashCode() ?
Actuellement donc pour générer un hashCode()
correct par rapport à notre méthode equals() vue précédemment, il faudrait utiliser un code comme celui-ci :
@Override
public int hashCode() {
// On choisit les deux nombres impairs
int result = 7;
final int multiplier = 17;
// Pour chaque attribut, on calcule le hashcode
// que l'on ajoute au résultat après l'avoir multiplié
// par le nombre "multiplieur" :
result = multiplier * result + attribut1;
result = multiplier * result
+ (attribut2 == null ? 0 : attribut2.hashCode());
// On retourne le résultat :
return result;
}
On pourra bientôt se contenter d’utiliser Objects.hash()
en lui spécifiant les attributs à prendre en compte :
@Override
public int hashCode() {
return Objects.hash( this.attribut1, this.attribut2 );
}
Simple et efficace !
Ne pas accepter les valeurs nulles
Enfin, on disposera de deux méthodes nonNull()
permettant de gérer les cas inverse, c’est à dire de provoquer une exception si son attribut est null
.
En effet, la gestion des valeurs nulles est très importante mais souvent erroné. On retrouve bien souvent des codes qui n’acceptent pas de telles valeurs, mais qui ne gère pas le cas, par exemple :
class MyObject {
private final String id;
private final String name;
public MyObject(String id, String name) {
this.id = id;
this.name = name;
}
}
Si les attributs ne doivent pas être null
, on doit s’assurer de cela au plus tôt afin de remonter le problème au plus tôt, ce qui permettra de le corriger plus rapidement. Concrètement il faut vérifier la nullité de ces valeurs :
class MyObject {
private final String id;
private final String name;
public MyObject(String id, String name) {
if (id==null) throw new NullPointerException("id");
if (name==null) throw new NullPointerException("name");
this.id = id;
this.name = name;
}
}
La classe Objects
nous propose donc deux méthodes nonNull()
nous permettant de gérer cela simplement :
static <T> T nonNull(T obj)
provoquera unNullPointerException
siobj
estnull
.static <T> T nonNull(T obj, String message)
fera de même, mais en personnalisant le message de l’exception.
Les méthodes sont paramétrées afin de retourner l’objet passé en paramètre lorsqu’il n’est pas null
, ce qui nous permet d’effectuer la vérification dès l’affectation :
class MyObject {
private final String id;
private final String name;
public MyObject(String id, String name) {
this.id = Objects.nonNull(id, "id");
this.name = Objects.nonNull(name, "name");
}
}
Simple et efficace…
4 Commentaires + Ajouter un commentaire
Tutoriels
Discussions
- jre 1.5, tomcat 6.0 et multi processeurs
- Possibilité d'accéder au type générique en runtime
- Classes, méthodes private
- Difference de performances Unix/Windows d'un programme?
- L'apparition du mot-clé const est-il prévu dans une version à venir du JDK?
- [REFLEXION] Connaitre toutes les classes qui implémentent une interface
- [ fuite ] memoire
- Définition exacte de @Override
- Recuperation du nom des parametres
Probablement un retour indirect des classes privées de JavaFX 1.x qui faisaient ce genre de choses. Bon, manque plus que l’opérateur Elvis dans Java8 et c’est bon…
Et concernant les tableaux, il y a déjà pas mal de méthodes similaires dans la classe Arrays, par ex. equals : http://java.sun.com/javase/6/docs/api/java/util/Arrays.html#equals(java.lang.Object,%20java.lang.Object) et d’autres deepMachin.
Sur les objets simples, par contre, cette classe sera la bienvenue.
Oui et il y a la même chose dans les Jakarta Commons…
Le problème c’est qu’on ne peut pas forcément inclure une librairie juste pour cela, donc c’est pratique d’avoir cela en standard
a++
dans l’ensemble, ca ressemble fortement a ce que fait google avec guava
http://guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/common/base/Objects.html