Object est la classe mère de toutes les classes en Java. Elle fournit plusieurs méthodes fondamentales que toutes les classes héritent automatiquement. Dans ce chapitre, nous allons étudier deux de ces méthodes essentielles : toString() qui permet de représenter un objet sous forme de chaîne de caractères, et equals() qui permet de comparer deux objets pour déterminer s'ils sont égaux. Ces méthodes sont essentielles pour le débogage, l'affichage et la comparaison d'objets dans vos programmes Java.
4.4Méthodes de la classe Object
4.4.1 – Méthode toString()
La méthode toString() est une méthode spéciale héritée de la classe Object (la classe mère de toutes les classes en Java). Elle permet de représenter un objet sous forme de chaîne de caractères. Cette méthode est automatiquement appelée dans plusieurs situations, notamment lors de l'affichage d'un objet. Redéfinir toString() est une bonne pratique qui améliore considérablement la lisibilité et le débogage de votre code.
🔍 Comportement par défaut
Si vous ne redéfinissez pas toString(), Java utilise la version héritée de Object, qui retourne une chaîne peu informative composée du nom de la classe, du symbole @ et du code de hachage hexadécimal de l'objet (adresse mémoire).
Exemple : Sans redéfinition de toString()
public class Personne {
String nom;
int age;
String ville;
}
// Utilisation
Personne p = new Personne();
p.nom = "Jean";
p.age = 25;
p.ville = "Paris";
System.out.println(p);
// Affiche : Personne@15db9742 (peu utile !)
// Format : NomClasse@codeHashHexadécimal
toString() dans toutes vos classes.
📝 Redéfinir toString()
Pour avoir une représentation claire et utile de vos objets, vous devez redéfinir la méthode toString() dans votre classe. Utilisez l'annotation @Override pour indiquer que vous redéfinissez une méthode héritée. Cette annotation aide le compilateur à détecter les erreurs si vous faites une faute de frappe dans le nom de la méthode.
Structure de base
La signature de la méthode doit être exactement :
@Override
public String toString() {
// Retourner une String représentant l'objet
return "...";
}
- Modificateur :
public(doit ĂŞtre public pour ĂŞtre accessible) - Type de retour :
String(obligatoire) - Paramètres : Aucun paramètre
- Annotation :
@Override(recommandée mais optionnelle)
Exemple : Redéfinition de toString()
public class Personne {
String nom;
int age;
String ville;
@Override
public String toString() {
return "Personne{nom='" + nom + "', age=" + age + ", ville='" + ville + "'}";
}
}
// Utilisation
Personne p = new Personne();
p.nom = "Jean";
p.age = 25;
p.ville = "Paris";
System.out.println(p);
// Affiche : Personne{nom='Jean', age=25, ville='Paris'}
// Beaucoup plus informatif que Personne@15db9742 !
- L'annotation
@Overrideindique qu'on redéfinit une méthode héritée - La méthode retourne une
Stringqui contient les informations importantes de l'objet - Le format utilisé (avec accolades) est un style courant et lisible
- Les valeurs des attributs sont intégrées dans la chaîne retournée
🎨 Formats de toString()
Vous pouvez formater votre toString() de différentes manières selon vos besoins. Le choix du format dépend du contexte d'utilisation : débogage, logs, affichage utilisateur, etc. Voici plusieurs styles courants :
Format 1 : Style simple et concis
Idéal pour un affichage rapide et lisible :
public class Personne {
String nom;
int age;
@Override
public String toString() {
return nom + " (" + age + " ans)";
}
}
// Utilisation
Personne p = new Personne();
p.nom = "Jean";
p.age = 25;
System.out.println(p);
// Affiche : Jean (25 ans)
Format 2 : Style avec accolades (style JSON)
Très courant en Java, ressemble à du JSON. Idéal pour le débogage :
public class Personne {
String nom;
int age;
String ville;
@Override
public String toString() {
return "Personne{nom='" + nom + "', age=" + age + ", ville='" + ville + "'}";
}
}
// Utilisation
Personne p = new Personne();
p.nom = "Jean";
p.age = 25;
p.ville = "Paris";
System.out.println(p);
// Affiche : Personne{nom='Jean', age=25, ville='Paris'}
Format 3 : Style multi-lignes
Utile pour des objets complexes avec beaucoup d'attributs :
public class Personne {
String nom;
int age;
String ville;
String email;
@Override
public String toString() {
return "Personne:\n" +
" Nom: " + nom + "\n" +
" Âge: " + age + " ans\n" +
" Ville: " + ville + "\n" +
" Email: " + email;
}
}
// Utilisation
Personne p = new Personne();
p.nom = "Jean";
p.age = 25;
p.ville = "Paris";
p.email = "jean@example.com";
System.out.println(p);
// Affiche :
// Personne:
// Nom: Jean
// Âge: 25 ans
// Ville: Paris
// Email: jean@example.com
Format 4 : Style avec StringBuilder (pour performances)
Pour les objets avec beaucoup d'attributs, utiliser StringBuilder est plus efficace :
public class Personne {
String nom;
int age;
String ville;
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Personne{");
sb.append("nom='").append(nom).append('\'');
sb.append(", age=").append(age);
sb.append(", ville='").append(ville).append('\'');
sb.append('}');
return sb.toString();
}
}
toString().
🔄 Quand toString() est appelée automatiquement
La méthode toString() est appelée automatiquement dans plusieurs situations. Java convertit implicitement l'objet en String en appelant toString(). Voici les principales situations :
1. Avec System.out.println() et System.out.print()
Personne p = new Personne();
p.nom = "Jean";
p.age = 25;
System.out.println(p); // Appelle toString() automatiquement
// Équivalent à : System.out.println(p.toString());
2. Avec la concaténation de chaînes
Personne p = new Personne();
p.nom = "Jean";
String message = "Personne : " + p;
// Appelle toString() automatiquement sur p
// Équivalent à : "Personne : " + p.toString()
System.out.println(message); // Affiche : Personne : Personne{nom='Jean', ...}
3. Avec String.format() et printf
Personne p = new Personne();
p.nom = "Jean";
System.out.printf("Info : %s%n", p);
// Appelle toString() automatiquement pour le %s
String.format("Personne : %s", p);
// Appelle aussi toString() automatiquement
4. Dans les collections (List, Set, Map, etc.)
List<Personne> personnes = new ArrayList<>();
Personne p1 = new Personne();
p1.nom = "Jean";
Personne p2 = new Personne();
p2.nom = "Marie";
personnes.add(p1);
personnes.add(p2);
System.out.println(personnes);
// Appelle toString() pour chaque élément de la liste
// Affiche : [Personne{nom='Jean', ...}, Personne{nom='Marie', ...}]
5. Dans les expressions de débogage
Personne p = new Personne();
p.nom = "Jean";
// Dans les logs
System.out.println("État de l'objet : " + p);
// Appelle toString() automatiquement
// Dans les assertions (si activées)
assert p != null : "L'objet est null : " + p;
// Appelle toString() si l'assertion échoue
Quand Java rencontre un objet dans un contexte où une String est attendue (concaténation, affichage, formatage), il appelle automatiquement toString() pour convertir l'objet en chaîne. C'est ce qu'on appelle la conversion implicite ou coercion de type.
đź’ˇ Avantages de toString()
Redéfinir toString() apporte de nombreux avantages à votre code :
- Débogage facilité : Vous pouvez rapidement voir l'état d'un objet en l'affichant, sans avoir à inspecter chaque attribut individuellement
- Lisibilité améliorée : Les logs et messages d'erreur deviennent beaucoup plus compréhensibles
- Développement plus rapide : Pas besoin d'écrire des méthodes d'affichage personnalisées pour chaque classe
- Collections : L'affichage des listes, sets et maps devient automatiquement lisible
- Documentation informelle : La méthode
toString()sert de documentation sur la structure et les attributs importants de la classe - Tests unitaires : Facilite la comparaison et l'affichage des objets dans les tests
- Redéfinissez toujours
toString()dans vos classes personnalisées - Incluez tous les attributs importants dans la représentation
- N'incluez PAS d'informations sensibles (mots de passe, numéros de carte bancaire, etc.)
- Utilisez un format cohérent dans toute votre application
- Évitez les opérations coûteuses dans
toString()(pas de calculs complexes, pas d'accès réseau, etc.)
📊 Exemple complet : Classe Voiture avec toString()
Voici un exemple complet d'une classe avec une méthode toString() bien implémentée :
public class Voiture {
private String marque;
private String modele;
private int annee;
private double prix;
private boolean estDisponible;
public Voiture(String marque, String modele, int annee, double prix) {
this.marque = marque;
this.modele = modele;
this.annee = annee;
this.prix = prix;
this.estDisponible = true;
}
// Getters
public String getMarque() { return marque; }
public String getModele() { return modele; }
public int getAnnee() { return annee; }
public double getPrix() { return prix; }
public boolean isEstDisponible() { return estDisponible; }
// Méthode toString() redéfinie
@Override
public String toString() {
return "Voiture{" +
"marque='" + marque + '\'' +
", modele='" + modele + '\'' +
", annee=" + annee +
", prix=" + prix + " €" +
", estDisponible=" + estDisponible +
'}';
}
}
// Utilisation
public class Garage {
public static void main(String[] args) {
Voiture v1 = new Voiture("Peugeot", "208", 2023, 25000.0);
Voiture v2 = new Voiture("Renault", "Clio", 2022, 22000.0);
// Affichage automatique avec toString()
System.out.println("Voiture 1 : " + v1);
// Affiche : Voiture 1 : Voiture{marque='Peugeot', modele='208', annee=2023, prix=25000.0 €, estDisponible=true}
System.out.println("Voiture 2 : " + v2);
// Affiche : Voiture 2 : Voiture{marque='Renault', modele='Clio', annee=2022, prix=22000.0 €, estDisponible=true}
// Dans une liste
List<Voiture> voitures = new ArrayList<>();
voitures.add(v1);
voitures.add(v2);
System.out.println("Liste des voitures : " + voitures);
// Affiche : Liste des voitures : [Voiture{marque='Peugeot', ...}, Voiture{marque='Renault', ...}]
}
}
- Tous les attributs importants sont inclus dans
toString() - Le format est cohérent et lisible (style JSON)
- Les valeurs numériques sont formatées avec des unités (€)
- L'affichage dans une liste est automatiquement lisible
- La méthode facilite grandement le débogage
❌ Erreurs communes à éviter
Erreur 1 : Oublier de retourner une String
// ❌ INCORRECT - Erreur de compilation
@Override
public String toString() {
System.out.println("Personne : " + nom); // Ne retourne rien !
}
// âś… CORRECT
@Override
public String toString() {
return "Personne{nom='" + nom + "'}";
}
Erreur 2 : Inclure des informations sensibles
// ❌ DANGEREUX - Ne jamais faire ça !
@Override
public String toString() {
return "Utilisateur{nom='" + nom + "', motDePasse='" + motDePasse + "'}";
// Le mot de passe pourrait être affiché dans les logs !
}
// âś… CORRECT - Exclure les informations sensibles
@Override
public String toString() {
return "Utilisateur{nom='" + nom + "', email='" + email + "'}";
}
Erreur 3 : Opérations coûteuses dans toString()
// ❌ MAUVAIS - Opération coûteuse
@Override
public String toString() {
// Ne faites pas d'appels réseau, de calculs complexes, etc.
String resultat = appelerAPIExterne(); // Très lent !
return "Personne{resultat='" + resultat + "'}";
}
// âś… CORRECT - Simple et rapide
@Override
public String toString() {
return "Personne{nom='" + nom + "', age=" + age + "}";
}
💡 Points clés à retenir
- Héritage :
toString()est héritée deObject, toutes les classes l'ont - Redéfinition : Utilisez
@Overridepour redéfinir la méthode (bonne pratique) - Signature :
public String toString()- pas de paramètres, retourne String - Appel automatique : Appelée automatiquement par
System.out.println(), concaténation, formatage, collections - Format : Choisissez un format clair et cohérent (style JSON recommandé)
- Utilité : Essentielle pour le débogage, les logs et l'affichage
- Sécurité : N'incluez jamais d'informations sensibles
- Performance : Gardez la méthode simple et rapide
- Redéfinissez toujours
toString()dans vos classes personnalisées - Incluez tous les attributs importants dans la représentation
- Utilisez un format cohérent dans toute votre application (style JSON recommandé)
- La plupart des IDE peuvent générer automatiquement
toString()(IntelliJ : Alt+Insert, Eclipse : Source → Generate toString()) - Testez votre
toString()en affichant vos objets dans différents contextes - Évitez d'inclure des informations sensibles (mots de passe, tokens, etc.)
4.4.2 – Méthode equals()
La méthode equals() est une autre méthode fondamentale héritée de la classe Object. Elle permet de comparer deux objets pour déterminer s'ils sont égaux selon votre définition de l'égalité. Contrairement à l'opérateur == qui compare les références (adresses mémoire), equals() compare le contenu ou l'état des objets.
🔍 Différence entre == et equals()
Il est crucial de comprendre la différence entre l'opérateur == et la méthode equals() :
- Compare les références (adresses mémoire)
- Retourne
truesi les deux variables pointent vers le même objet en mémoire - Ne compare PAS le contenu des objets
- Compare le contenu ou l'état des objets
- Retourne
truesi les objets ont les mêmes valeurs d'attributs (selon votre définition) - Peut être redéfinie pour définir votre propre logique d'égalité
Exemple : Comparaison avec ==
Personne p1 = new Personne();
p1.nom = "Jean";
p1.age = 25;
Personne p2 = new Personne();
p2.nom = "Jean";
p2.age = 25;
Personne p3 = p1; // p3 pointe vers le mĂŞme objet que p1
System.out.println(p1 == p2); // false - ce sont deux objets différents en mémoire
System.out.println(p1 == p3); // true - p1 et p3 pointent vers le mĂŞme objet
Exemple : Comparaison avec equals() (sans redéfinition)
Personne p1 = new Personne();
p1.nom = "Jean";
p1.age = 25;
Personne p2 = new Personne();
p2.nom = "Jean";
p2.age = 25;
// Sans redéfinition de equals(), utilise la version de Object
System.out.println(p1.equals(p2)); // false - compare les références (comme ==)
System.out.println(p1 == p2); // false - même résultat
equals() de la classe Object compare les références (comme ==). Pour comparer le contenu des objets, vous devez redéfinir equals() dans votre classe.
📝 Redéfinir equals()
Pour comparer le contenu de vos objets, vous devez redéfinir la méthode equals(). Voici la structure de base :
Structure de base
@Override
public boolean equals(Object obj) {
// 1. Vérifier si c'est le même objet (référence)
if (this == obj) {
return true;
}
// 2. Vérifier si l'objet est null
if (obj == null) {
return false;
}
// 3. Vérifier si c'est le même type de classe
if (this.getClass() != obj.getClass()) {
return false;
}
// 4. Caster et comparer les attributs
Personne autre = (Personne) obj;
return this.nom.equals(autre.nom) && this.age == autre.age;
}
Exemple complet : Redéfinition de equals()
public class Personne {
String nom;
int age;
String ville;
@Override
public boolean equals(Object obj) {
// 1. Vérifier si c'est le même objet (optimisation)
if (this == obj) {
return true;
}
// 2. Vérifier si l'objet est null
if (obj == null) {
return false;
}
// 3. Vérifier si c'est le même type de classe
if (this.getClass() != obj.getClass()) {
return false;
}
// 4. Caster en Personne
Personne autre = (Personne) obj;
// 5. Comparer les attributs importants
return this.nom.equals(autre.nom) &&
this.age == autre.age &&
this.ville.equals(autre.ville);
}
}
// Utilisation
Personne p1 = new Personne();
p1.nom = "Jean";
p1.age = 25;
p1.ville = "Paris";
Personne p2 = new Personne();
p2.nom = "Jean";
p2.age = 25;
p2.ville = "Paris";
Personne p3 = new Personne();
p3.nom = "Marie";
p3.age = 25;
p3.ville = "Paris";
System.out.println(p1.equals(p2)); // true - mĂŞme contenu
System.out.println(p1.equals(p3)); // false - contenu différent
System.out.println(p1 == p2); // false - objets différents en mémoire
- Vérification de référence : Si c'est le même objet, retourne
trueimmĂ©diatement (optimisation) - VĂ©rification null : Un objet n'est jamais Ă©gal Ă
null - Vérification de type : Les objets doivent être de la même classe
- Cast : Convertir
Objecten type spécifique - Comparaison d'attributs : Comparer les valeurs importantes pour déterminer l'égalité
đź“‹ Contrat de equals()
La méthode equals() doit respecter un contrat strict en Java. Si vous redéfinissez equals(), vous devez respecter ces règles :
- Réflexivité :
x.equals(x)doit toujours retournertrue - Symétrie : Si
x.equals(y)retournetrue, alorsy.equals(x)doit aussi retournertrue - Transitivité : Si
x.equals(y)ety.equals(z)retournenttrue, alorsx.equals(z)doit aussi retournertrue - CohĂ©rence : Plusieurs appels Ă
equals()doivent retourner le même résultat si les objets ne changent pas - Non-nullité :
x.equals(null)doit toujours retournerfalse
🎯 Exemple complet : Classe Etudiant avec equals()
public class Etudiant {
private String numeroEtudiant;
private String nom;
private int age;
public Etudiant(String numeroEtudiant, String nom, int age) {
this.numeroEtudiant = numeroEtudiant;
this.nom = nom;
this.age = age;
}
// Getters
public String getNumeroEtudiant() { return numeroEtudiant; }
public String getNom() { return nom; }
public int getAge() { return age; }
@Override
public boolean equals(Object obj) {
// 1. Même référence ?
if (this == obj) {
return true;
}
// 2. Null ?
if (obj == null) {
return false;
}
// 3. MĂŞme classe ?
if (this.getClass() != obj.getClass()) {
return false;
}
// 4. Cast
Etudiant autre = (Etudiant) obj;
// 5. Comparer le numéro étudiant (identifiant unique)
return this.numeroEtudiant.equals(autre.numeroEtudiant);
}
@Override
public String toString() {
return "Etudiant{numero='" + numeroEtudiant + "', nom='" + nom + "', age=" + age + "}";
}
}
// Utilisation
public class GestionEtudiants {
public static void main(String[] args) {
Etudiant e1 = new Etudiant("E001", "Jean", 20);
Etudiant e2 = new Etudiant("E001", "Jean", 20); // Même numéro
Etudiant e3 = new Etudiant("E002", "Marie", 21); // Numéro différent
System.out.println(e1.equals(e2)); // true - même numéro étudiant
System.out.println(e1.equals(e3)); // false - numéros différents
System.out.println(e1 == e2); // false - objets différents en mémoire
// Dans une liste
List<Etudiant> etudiants = new ArrayList<>();
etudiants.add(e1);
// VĂ©rifier si un Ă©tudiant existe dĂ©jĂ
if (etudiants.contains(e2)) { // Utilise equals() en interne
System.out.println("L'étudiant existe déjà !");
}
}
}
- Dans cet exemple, deux étudiants sont considérés égaux s'ils ont le même numéro étudiant
- Le nom et l'âge ne sont pas utilisés pour la comparaison (seul le numéro compte)
- Les collections Java (
List,Set, etc.) utilisentequals()pour vérifier l'existence d'éléments - La méthode
contains()d'une liste utiliseequals()en interne
đź”— equals() et les collections
La méthode equals() est cruciale pour le fonctionnement des collections Java :
List<Personne> personnes = new ArrayList<>();
Personne p1 = new Personne();
p1.nom = "Jean";
Personne p2 = new Personne();
p2.nom = "Jean"; // MĂŞme nom que p1
personnes.add(p1);
// contains() utilise equals() en interne
System.out.println(personnes.contains(p2));
// Si equals() est bien redéfinie : true
// Si equals() n'est pas redéfinie : false (compare les références)
// indexOf() utilise aussi equals()
int index = personnes.indexOf(p2);
// Retourne l'index si equals() retourne true, sinon -1
❌ Erreurs communes à éviter
Erreur 1 : Oublier de vérifier null
// ❌ DANGEREUX - NullPointerException possible
@Override
public boolean equals(Object obj) {
Personne autre = (Personne) obj; // Erreur si obj est null !
return this.nom.equals(autre.nom);
}
// âś… CORRECT
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
Personne autre = (Personne) obj;
return this.nom.equals(autre.nom);
}
Erreur 2 : Comparer avec == au lieu de equals() pour les String
// ❌ INCORRECT - Compare les références, pas le contenu
@Override
public boolean equals(Object obj) {
if (obj == null || this.getClass() != obj.getClass()) {
return false;
}
Personne autre = (Personne) obj;
return this.nom == autre.nom; // ❌ Ne compare pas le contenu des String !
}
// âś… CORRECT - Utilise equals() pour les String
@Override
public boolean equals(Object obj) {
if (obj == null || this.getClass() != obj.getClass()) {
return false;
}
Personne autre = (Personne) obj;
return this.nom.equals(autre.nom); // âś… Compare le contenu
}
Erreur 3 : Oublier de vérifier le type de classe
// ❌ DANGEREUX - ClassCastException possible
@Override
public boolean equals(Object obj) {
Personne autre = (Personne) obj; // Erreur si obj n'est pas une Personne !
return this.nom.equals(autre.nom);
}
// âś… CORRECT
@Override
public boolean equals(Object obj) {
if (obj == null || this.getClass() != obj.getClass()) {
return false;
}
Personne autre = (Personne) obj;
return this.nom.equals(autre.nom);
}
💡 Points clés à retenir
- Différence == vs equals() :
==compare les références,equals()compare le contenu - Redéfinition nécessaire : Par défaut,
equals()compare les références (comme==) - Signature :
public boolean equals(Object obj)- prend unObject, retourneboolean - Contrat : Doit respecter les règles de réflexivité, symétrie, transitivité, cohérence et non-nullité
- Collections : Les collections Java utilisent
equals()pour vérifier l'existence d'éléments - String : Utilisez toujours
.equals()pour comparer des String, jamais== - Vérifications : Toujours vérifier null et le type de classe avant de caster
- Redéfinissez
equals()si vous voulez comparer le contenu de vos objets - Définissez clairement quels attributs déterminent l'égalité (identifiant unique, tous les attributs, etc.)
- Utilisez
@Overridepour indiquer que vous redéfinissez la méthode - Testez votre
equals()avec différents cas (même objet, null, type différent, contenu identique, contenu différent) - La plupart des IDE peuvent générer automatiquement
equals()(IntelliJ : Alt+Insert, Eclipse : Source → Generate hashCode() and equals()) - Si vous redéfinissez
equals(), vous devriez aussi redéfinirhashCode()(nous verrons cela plus tard)