Toutes les classes Java héritent automatiquement tout ce qui est défini dans la classe Object
, même les classes que vous écrivez. Cette classe contient plusieurs méthodes utiles que les développeurs tendent à réécrire, ce qu’on appelle supplanter une méthode, pour les adapter aux nouveaux objets.
Object
(parent de toutes les classes Java). On utilise le décorateur @Override
pour signaler explicitement qu’on tente de supplanter une méthode déjà définie. Des synonymes pour “supplanter” sont “remplacer” ou “surclasser”.this([paramètres])
pour éviter de dupliquer le code écrit dans le constructeur plus général.String toString()
Object
) affiche le nom de la classe suivi par un code hexadécimal. Il est souvent préférable de supplanter cette méthode pour afficher des informations plus utiles.boolean equals(Object)
et int hashCode()
equals
compare le contenu de deux objets pour déterminer s’ils sont égaux. La méthode hashCode
retourne un entier qui identifie l’objet. La classe Object
spécifie un contrat entre ces deux méthodes : si deux objets retournent vrai avec equals
, ils doivent aussi retourner le même entier avec hashCode
. Il est donc important de supplanter ces deux méthodes ensemble. Les EDI modernes peuvent générer ces méthodes automatiquement pour vous.À la fin de cette leçon vous devrez être en mesure de :
toString
.hashCode
et equals
ensemble.this()
.toString
qui affiche des informations utiles sur mes objets.hashCode
et equals
dans mes propres objets avec l’aide de l’EDI.On définit une méthode spéciale qui s’appelle un constructeur afin d’initialiser les objets d’une manière spécifique. La signature du constructeur est simplement le nom de la classe suivi par des parenthèses, avec ou sans paramètres.
1
2
3
4
5
6
7
8
class ShinyObject {
boolean isShiny;
/** Constructeur */
ShinyObject() {
isShiny = true; // initialise avec une valeur littérale
}
}
Les constructeurs sont généralement surchargés, c’est-à-dire définis plusieurs fois avec différents paramètres. On définit souvent une version sans paramètres et une version avec un paramètre par attribut afin d’initialiser chaque attribut dès la création d’une instance de l’objet.
Pour éviter de dupliquer le code dans les constructeurs, c’est une bonne pratique d’appeler le constructeur le plus complet dans les autres constructeurs. On le fait avec la méthode this([paramètres,...])
.
1
2
3
4
5
6
7
8
9
10
11
12
13
class ShinyObject {
boolean isShiny;
/** Constructer avec paramètre */
ShinyObject(boolean isShiny) {
this.isShiny = isShiny; // initialise avec le paramètre
}
/** Constructeur sans paramètres */
ShinyObject() {
this(true); // passe la valeur par défaut comme argument
}
}
Le constructueur par défaut (définit dans la classe
Object
) est un constructeur sans paramètres qui assigne la valeur par défaut aux attributs :
0
pour les nombres,''
pour les char,false
pour les boolean etnull
pour les objets, comme lesString
.
String toString()
La méthode toString
produit une représentation textuelle de l’objet, normalement en affichant de manière conviviale les valeurs des attributs. La définition par défaut affiche le nom de la classe suivi par un code hexadécimal.
1
ShinyObject@2c7b84de
C’est définitivement mieux de supplanter (remplacer, surclasser) cette définition!
Reprenant la classe ShinyObject
ci-dessus, on pourrait lui ajouter la méthode toString
suivante :
1
2
3
4
@Override
public String toString(){
return "Est-ce que cet objet est reluisant? " + isShiny;
}
Notez que la méthode retourne un String mais ne l’affiche pas (pas de System.out.println()).
Notez aussi qu’on précède la signature de la méthode avec le décorateur @Override
afin de signaler plus explicitement qu’on tente de supplanter une méthode déjà définie dans la classe Object
. Le décorateur est optionnel mais force le compilateur à vérifier que vous avez bien redéfinie une méthode existante, p. ex.: vous n’avez pas tapez tostring
par accident ou mégarde au lieu de toString
.
Pour les valeurs décimales, c’est souvent une bonne idée d’utiliser un texte formaté pour limiter le nombre de places après la virgule à une précision spécifique. Prenons l’exemple suivant :
1
2
3
4
5
6
7
8
9
class Student {
String name;
double average;
@Override
public String toString() {
return String.format("%s a une moyenne de %.2f", name, average);
}
}
On remarque plusieurs choses ici :
String String.format(String, [valeurs])
pour produire le texte et non la concaténation de texte et de valeurs.%s
et le %0.2f
. Ces spécifications font trois choses :
s
ou S
pour les String
, d
pour les int
, f
pour les double
ou float
, etc.%.2f
, le .2
signale d’afficher seulement deux places après la virgule.Pour plus de détails sur les spécifications et options de format possibles, voir la documentation pour la classe
Formatter
.
boolean equals(Object)
et int hashCode()
La comparaison ==
donne vrai si les deux objets occupent le même bloc en mémoire. Ça marche pour les valeurs primitives (int
, double
, char
, etc.) parce que chaque nouvelle valeur primitive possible prend une nouvelle place en mémoire.
Pour les objets, par contre, chaque instance occupe une nouvelle place en mémoire, peu importe si les valeurs des attributs sont identiques. C’est pourquoi on utilise la méthode equals
pour comparer le texte de deux Strings. En fait, il faut utiliser cette méthode pour comparer le contenu de tous les types d’objets, pas juste les String.
La classe Object
spécifie un contrat entre la méthode equals
et une autre méthode, hashCode
: il faut que deux instances qui retournent vrai avec le méthode equals retournent aussi le même int avec la méthode hashCode. Ça devient donc important de définir les deux méthodes ensemble.
Voici un exemple (attachez vos tuques!) :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Student {
String name;
double average;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
long temp;
temp = Double.doubleToLongBits(average);
result = prime * result + (int) (temp ^ (temp >>> 32));
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
// clauses de garde
if (this == obj) // l'autre pointe au même bloc de mémoire
return true;
if (obj == null) // l'autre pointe à rien
return false;
if (getClass() != obj.getClass()) // l'autre n'est pas du même type
return false;
// créer une instance du bon type avec l'autre Object
Square other = (Square) obj;
// comparaison des attributs des objets
if (Double.doubleToLongBits(average) != Double.doubleToLongBits(other.average))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
Première chose à dire : ces deux méthodes ont été générées automatiquement par l’EDI VS Code. D’autres EDI, comme Eclipse et IntelliJ, font ça aussi. C’est la façon la plus simple de les produire, surtout que c’est seulement la section “Comparaison des attributs des objets” dans la méthode equals
qui pourrait (peut-être) nécessiter un ajustement si vous aviez envi de faire une comparaison particulière. Le reste dans equals
et dans hashCode
doit être là. Et, dans ce cours, ce n’est pas nécessaire de comprendre tous les détails.
Alors voici comment générer ces méthodes automatiquement avec VS Code (avec le “Extension Pack for Java” développé par Microsoft, vscjava.vscode-java-pack
) :
Ctrl + .
et consultez la section “More Actions”equals()
si vous avez une autre comparaison que vous préférez implémenter. Refaire les tests après toute modification.Quiz de vérification sur les méthodes typiques d’Objects Java
Écrivez deux constructeurs pour votre objet, un sans paramètres qui utilise l’appel this([paramètres])
pour appeler le deuxième qui est défini avec les paramètres nécessaires pour initialiser les attributs de l’objet.
Supplanter les méthodes hashCode
, equals
et toString
pour votre objet.
Testez ces nouvelles méthodes dans la classe pilote.