ICS4U

Accueil > Classes et objets >

📚 Méthodes typiques d’une classe Java

Survol et attentes

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.

Définitions
Surcharger une méthode
Définir plusieurs fois une méthode avec le même nom mais avec des paramètres différents. Java choisit la version de la méthode à appeler en fonction des paramètres passés à la méthode.
Supplanter une méthode
Redéfinir une méthode déjà définie dans une classe parente, souvent dans la classe 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”.
Constructeur
Méthode spéciale qui initialise les objets d’une manière spécifique. Le nom d’un constructeur est le même que le nom de la classe. C’est commun de surcharger le constructeur : un constructeur par défaut (sans paramètres) qui appelle le constructeur plus général en lui passant des valeurs par défaut comme arguments. On utilise l’appel this([paramètres]) pour éviter de dupliquer le code écrit dans le constructeur plus général.
Méthode String toString()
Méthode qui produit une représentation textuelle de l’objet. Cette méthode est appellé automatiquement quand on tente d’afficher un objet, mais la définition par défaut (dans 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.
Méthodes boolean equals(Object) et int hashCode()
La méthode 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.

Objectifs d’apprentissage

À la fin de cette leçon vous devrez être en mesure de :

Critères de succès

Constructeurs

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 et
  • null pour les objets, comme les String.

Méthode 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 :

  • On utilise la méthode String String.format(String, [valeurs]) pour produire le texte et non la concaténation de texte et de valeurs.
  • Le String intègre des spécifications de format directement dans le texte - le %s et le %0.2f. Ces spécifications font trois choses :
    • la lettre à la fin correspond à un type de données : s ou S pour les String, d pour les int, f pour les double ou float, etc.
    • les caractères entre le % et la lettre donne un format optionnel. Dans le cas du %.2f, le .2 signale d’afficher seulement deux places après la virgule.
    • la présence de chaque spécification détermine combien et quels types de valeurs doivent suivre le String dans la méthode : il faut une valeur du bon type pour chaque spécification de format. Ces valeurs doivent suivre dans le même ordre que l’ordre d’apparence des spécifications dans le String.

Pour plus de détails sur les spécifications et options de format possibles, voir la documentation pour la classe Formatter.

Méthodes 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) :

  1. Cliquez sur le nom de la classe dans sa signature.
  2. Ici vous avez deux choix :
    1. Faites un clic droit sur le nom et cliquez ensuite sur l’option “Source Action…”
    2. Faites la combinaison Ctrl + . et consultez la section “More Actions”
  3. Cliquez sur “Generate hashCode() and equals()…”
  4. Sélectionnez les attributs à inclure dans leurs algorithmes et cliquez “OK”.
  5. Validez le code en faisant quelque tests avec différentes instances via la classe pilote ou une classe test.
  6. Optionnellement, ajustez le code dans equals() si vous avez une autre comparaison que vous préférez implémenter. Refaire les tests après toute modification.

Exercices

📚 Tester la compréhension

Quiz de vérification sur les méthodes typiques d’Objects Java

🛠️ Pratique

É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.