CodeGym /Blog Java /France /Getters et Setters en Java
Auteur
Vasyl Malik
Senior Java Developer at CodeGym

Getters et Setters en Java

Publié dans le groupe France
Bonjour ! Dans les leçons précédentes, tu as appris comment déclarer tes propres classes à part entière avec des champs et des méthodes. Cela représente de sérieux progrès, bien joué ! Mais maintenant, je dois t'avouer une triste vérité. Nous n'avons pas déclaré nos classes correctement ! Comment ça ? À première vue, il n'y a rien de mal à la classe suivante :

public class Cat {

   public String name;
   public int age;
   public int weight;

   public Cat(String name, int age, int weight) {
       this.name = name;
       this.age = age;
       this.weight = weight;
   }

   public Cat() {
   }

   public void sayMeow() {
       System.out.println("Meow!");
   }
}
Et pourtant. Imagine que tu es à ton travail et que tu écris cette classe Cat pour représenter des chats. Et puis tu rentres chez toi. Pendant ton absence, un autre programmeur arrive au travail. Il crée sa propre classe Main, où il commence à utiliser la classe Cat que tu as écrite.

public class Main {

   public static void main(String[] args) {

       Cat cat = new Cat();
       cat.name = "";
       cat.age = -1000;
       cat.weight = 0;
   }
}
Peu importe pourquoi il l'a fait et comment ça s'est produit (peut-être que ce type a besoin de sommeil). Nous avons un problème plus important : en l'état, notre classe Cat autorise que des valeurs absolument folles soient affectées à ses champs. En conséquence, le programme a des objets avec un état non valide (comme ce chat qui est âgé de -1000 ans). Alors quelle erreur avons-nous faite lors de la déclaration de notre classe ? Nous avons exposé les données de notre classe. Les champs name, age et weight sont public. Ils sont accessibles partout dans le programme : crée simplement un objet Cat, et tout programmeur aura un accès direct à ses données par l'intermédiaire de l'opérateur point (.).

Cat cat = new Cat();
cat.name = "";
Ici, nous accédons directement au champ name et définissons sa valeur. Nous devons nous débrouiller pour protéger nos données de l'ingérence malvenue d'étrangers. Comment y parvenir ? Tout d'abord, nous devons marquer toutes les variables d'instance (les champs) avec le modificateur private. private est le modificateur d'accès le plus strict en Java. Une fois que tu as fait cela, les champs de la classe Cat ne seront plus accessibles en dehors de la classe.

public class Cat {

   private String name;
   private int age;
   private int weight;

   public Cat(String name, int age, int weight) {
       this.name = name;
       this.age = age;
       this.weight = weight;
   }

   public Cat() {
   }

   public void sayMeow() {
       System.out.println("Meow!");
   }
}

public class Main {

   public static void main(String[] args) {

       Cat cat = new Cat();
       cat.name = "";//error! The Cat class's name field is private!
   }
}
Le compilateur voit cela et génère immédiatement une erreur. Maintenant, les champs sont plus ou moins protégés. Mais peut-être que nous avons un peu trop bien restreint l'accès : tu ne peux pas obtenir le poids d'un chat existant dans le programme, même si tu en as besoin. Cette solution n'est pas acceptable non plus. En l'état, notre classe est fondamentalement inutilisable. L'idéal serait de permettre une sorte d'accès limité :
  • D'autres programmeurs devraient être en mesure de créer des objets Cat.
  • Ils devraient être en mesure de lire les données à partir d'objets existants (par exemple, obtenir le nom ou l'âge d'un chat existant).
  • Il devrait également être possible d'affecter des valeurs à ces champs. Mais seules des valeurs valides doivent être autorisées. Nos objets doivent être protégés contre les valeurs non valides (age = -1000, et ce genre de loufoqueries)
Ouah, ça nous fait beaucoup d'exigences ! En réalité, tout cela est facile à réaliser avec les méthodes spéciales appelées getters et setters.
Getters et setters - 2
Ces noms proviennent de « get » (« une méthode pour obtenir la valeur d'un champ ») et « set » (« une méthode pour définir la valeur d'un champ »). Découvrons de quoi ces méthodes ont l'air dans notre classe Cat :

public class Cat {

   private String name;
   private int age;
   private int weight;

   public Cat(String name, int age, int weight) {
       this.name = name;
       this.age = age;
       this.weight = weight;
   }

   public Cat() {
   }

   public void sayMeow() {
       System.out.println("Meow!");
   }

   public String getName() {
       return name;
   }

   public void setName(String name) {
       this.name = name;
   }

   public int getAge() {
       return age;
   }

   public void setAge(int age) {
       this.age = age;
   }

   public int getWeight() {
       return weight;
   }

   public void setWeight(int weight) {
       this.weight = weight;
   }
}
Comme tu peux le voir, elles se présentent assez simplement :) Leurs noms se composent souvent de « get »/« set » et du nom du champ concerné. Par exemple, la méthode getWeight() renvoie la valeur du champ weight de l'objet sur lequel elle est appelée. Voici à quoi cela ressemble dans un programme :

public class Main {

   public static void main(String[] args) {

       Cat smudge = new Cat("Smudge", 5, 4);
       String smudgeName = smudge.getName();
       int smudgeAge = smudge.getAge();
       int smudgeWeight = smudge.getWeight();

       System.out.println("Cat's name: " + smudgeName);
       System.out.println("Cat's age: " + smudgeAge);
       System.out.println("Cat's weight: " + smudgeWeight);
   }
}
Sortie de la console :
Cat's name: Smudge
Cat's age: 5
Cat's weight: 4
Maintenant, une autre classe (Main) peut accéder aux champs de la classe Cat, mais seulement par le biais de getters. Remarque que les getters ont le modificateur d'accès public, ce qui signifie qu'ils sont disponibles depuis n'importe où dans le programme. Mais qu'en est-il de l'affectation de valeurs ? C'est à cela que les méthodes setter servent.

public void setName(String name) {
   this.name = name;
}
Comme tu peux le voir, elles sont également simples. Nous appelons une méthode setName() sur un objetCat, lui passons une chaîne comme argument, et la chaîne est affectée au champ name de l'objet.

public class Main {

   public static void main(String[] args) {

       Cat smudge = new Cat("Smudge", 5, 4);

       System.out.println("Cat's original name: " + smudge.getName());
       smudge.setName("Mr. Smudge");
       System.out.println("Cat's new name: " + smudge.getName());
   }
}
Ici, nous utilisons à la fois des getters et des setters. Tout d'abord, nous utilisons un getter pour obtenir et afficher le nom d'origine du chat. Ensuite, nous utilisons un setter pour lui affecter un nouveau nom dans le champ name (« M. Smudge »). Ensuite, nous utilisons le getter une nouvelle fois pour obtenir le nom (pour vérifier s'il a vraiment changé). Sortie de la console :
Cat's original name: Smudge
Cat's new name: Mr. Smudge
Alors quelle est la différence par rapport à ce que nous faisions avant ? Nous pouvons toujours affecter des valeurs non valides aux champs même si nous avons des setters :

public class Main {

   public static void main(String[] args) {

       Cat smudge = new Cat("Smudge", 5, 4);
       smudge.setAge(-1000);

       System.out.println("Smudge's age: " + smudge.getAge());
   }
}
Sortie de la console :
Smudge's age: -1000 years
La différence est qu'un setter est une méthode à part entière. Et contrairement à un champ, une méthode te permet d'écrire la logique de vérification nécessaire pour éviter d'avoir des valeurs inacceptables. Par exemple, tu peux facilement éviter l'affectation d'un nombre négatif au champ age :

public void setAge(int age) {
   if (age >= 0) {
       this.age = age;
   } else {
       System.out.println("Error! Age can't be negative!");
   }
}
Et maintenant notre code fonctionne correctement !

public class Main {

   public static void main(String[] args) {

       Cat smudge = new Cat("Smudge", 5, 4);
       smudge.setAge(-1000);

       System.out.println("Smudge's age: " + smudge.getAge());
   }
}
Sortie de la console :
Error! Age can't be negative!
Smudge's age: 5 years
Dans notre setter, nous avons ajouté une vérification qui nous a protégés de la tentative de définition de données non valides. L'âge de Smudge n'a pas été modifié. Tu dois toujours créer des getters et des setters. Même s'il n'y a aucune restriction sur les valeurs que tes champs peuvent recevoir, ces méthodes d'assistance ne feront pas de mal. Imagine la situation suivante : toi et tes collègues écrivez un programme ensemble. Tu crées une classe Cat avec des champs public : Tous les programmeurs les utilisent comme ils veulent. Et puis un beau jour, tu réalises : « Zut, tôt ou tard quelqu'un pourrait accidentellement affecter un nombre négatif au poids ! Nous devons créer des setters et rendre tous les champs private ! » Tu fais exactement ça, et tout le code écrit par tes collègues cesse instantanément de fonctionner. Après tout, ils ont déjà écrit un tas de code qui accède aux champs de la classe Cat directement.

cat.name = "Behemoth";
Mais maintenant, les champs sont private et le compilateur génère un tas d'erreurs !

cat.name = "Behemoth";//error! The Cat class's name field is private!
Dans ce cas, il serait préférable de masquer les champs et de créer des getters et setters dès le départ. Tous tes collègues les auraient utilisés. Et si tu as réalisé un peu tard que tu as besoin de restreindre les valeurs d'un champ d'une manière ou d'une autre, tu aurais simplement pu écrire la vérification à l'intérieur du setter. Et comme ça tu n'aurais cassé le code de personne. Bien sûr, si tu veux limiter un champ à un accès en « lecture seule », tu peux te contenter de créer un getter uniquement. Seules les méthodes devraient être disponibles à l'extérieur (c'est-à-dire en dehors de ta classe). Les données doivent être cachées. Nous pourrions comparer cela à un téléphone mobile. Imagine qu'au lieu d'un téléphone mobile habituel dans son boîtier, tu as un téléphone avec un boîtier ouvert, avec toutes sortes de fils et de circuits qui dépassent. Le téléphone fonctionne : si tu fais un gros effort et que tu tritures les circuits, tu pourrais même réussir à passer un appel. Mais tu risques surtout de le casser. Au lieu de cela, le fabricant te donne une interface : l'utilisateur saisit simplement les bons chiffres, appuie sur le bouton vert d'appel, et l'appel commence. Tu ne te soucies pas de ce qui se passe à l'intérieur des circuits et des fils, ou de comment ils font leur travail. Dans cet exemple, le fabricant limite l'accès aux « entrailles » du téléphone (données) et expose seulement une interface (méthodes). Ainsi, l'utilisateur obtient ce qu'il veut (la capacité de passer un appel) sans risque de casser ce qui se trouve à l'intérieur.
Cet article est également disponible en anglais:
Read the English version of this article to learn more about getters and setters in Java.
Commentaires (1)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
Lou niveau 6, Spain
2 septembre 2022
Very nice ! Thank you very much !