CodeGym/Blog Java/France/Les constructeurs en Java - Pourquoi avons-nous besoin de...
Auteur
Jesse Haniel
Lead Software Architect at Tribunal de Justiça da Paraíba

Les constructeurs en Java - Pourquoi avons-nous besoin de constructeurs ?

Publié dans le groupe France
membres
Coucou ! Aujourd'hui, nous allons aborder un sujet très important qui concerne nos objets. Sans exagération, nous pouvons dire que tu vas utiliser ce sujet dans la vraie vie tous les jours ! Nous parlons des constructeurs en Java. C'est peut-être la première fois que tu entends ce terme, mais tu as déjà utilisé des constructeurs. Tu ne l'avais tout simplement pas réalisé :) Nous nous en convaincrons plus tard. Les constructeurs en Java - Pourquoi avons-nous besoin de constructeurs ? - 1

Que sont-ils et pourquoi sont-ils nécessaires ?

Prenons deux exemples.
public class Car {

   String model;
   int maxSpeed;

   public static void main(String[] args) {

       Car bugatti = new Car();
       bugatti.model = "Bugatti Veyron";
       bugatti.maxSpeed = 378;

   }
}
Nous avons créé notre voiture, et défini son modèle et sa vitesse maximale. Mais l'objet Car n'aurait évidemment pas que 2 champs dans un vrai projet. Par exemple, il pourrait en avoir 16 !
public class Car {

   String model;// model
   int maxSpeed;// maximum speed
   int wheels;// wheel width
   double engineVolume;// engine volume
   String color;// color
   int productionYear;// production year
   String ownerFirstName;// first name of owner
   String ownerLastName;// last name of owner
   long price;// price
   boolean isNew;// flag indicating whether car is new
   int seatsInTheCar;// number of seats in the car
   String cabinMaterial;// interior material
   boolean insurance;// flag indicating whether car is insured
   String manufacturerCountry;// manufacturer country
   int trunkVolume;// size of the trunk
   int accelerationTo100km;// how long it takes to accelerate to 100 km/h (in seconds)


   public static void main(String[] args) {
       Car bugatti = new Car();

       bugatti.color = "blue";
       bugatti.accelerationTo100km = 3;
       bugatti.engineVolume = 6.3;
       bugatti.manufacturerCountry = "Italy";
       bugatti.ownerFirstName = "Amigo";
       bugatti.productionYear = 2016;
       bugatti.insurance = true;
       bugatti.price = 2000000;
       bugatti.isNew = false;
       bugatti.seatsInTheCar = 2;
       bugatti.maxSpeed = 378;
       bugatti.model = "Bugatti Veyron";

   }

}
Nous avons créé un nouvel objet Car. Mais il y a un problème : nous avons 16 champs, mais nous n'en avons initialisé que 12 ! Regarde le code maintenant et essaie de trouver les champs que nous avons oubliés ! Pas si simple, hein ? Dans cette situation, un programmeur peut facilement faire une erreur et oublier d'initialiser certains champs. En conséquence, le programme ne se comportera pas correctement :
public class Car {

   String model;// model
   int maxSpeed;// maximum speed
   int wheels;// wheel width
   double engineVolume;// engine volume
   String color;// color
   int productionYear;// production year
   String ownerFirstName;// first name of owner
   String ownerLastName;// last name of owner
   long price;// price
   boolean isNew;// flag indicating whether car is new
   int seatsInTheCar;// number of seats in the car
   String cabinMaterial;// interior material
   boolean insurance;// flag indicating whether car is insured
   String manufacturerCountry;// manufacturer country
   int trunkVolume;// size of the trunk
   int accelerationTo100km;// how long it takes to accelerate to 100 km/h (in seconds)


   public static void main(String[] args) {
       Car bugatti = new Car();

       bugatti.color = "blue";
       bugatti.accelerationTo100km = 3;
       bugatti.engineVolume = 6.3;
       bugatti.manufacturerCountry = "Italy";
       bugatti.ownerFirstName = "Amigo";
       bugatti.productionYear = 2016;
       bugatti.insurance = true;
       bugatti.price = 2000000;
       bugatti.isNew = false;
       bugatti.seatsInTheCar = 2;
       bugatti.maxSpeed = 378;
       bugatti.model = "Bugatti Veyron";

       System.out.println("Model: Bugatti Veyron. Engine volume: " + bugatti.engineVolume + ". Trunk volume: " + bugatti.trunkVolume + ". Cabin material: " + bugatti.cabinMaterial +
       ". Wheel width: " + bugatti.wheels + ". Purchased in 2018 by Mr. " + bugatti.ownerLastName);

   }

}
Sortie de la console :
Modèle : Bugatti Veyron. Volume du moteur : 6.3. Volume du coffre : 0. Matériau d'intérieur : null. Largeur de roue : 0. Achetée en 2018 par M. null
Votre acheteur, qui vient de lâcher 2 millions pour sa voiture, n'est évidemment pas très content de se faire appeler « M. null » ! Mais plus sérieusement, l'idée est que notre programme a créé un objet incorrectement : une voiture avec une largeur de roue de 0 (c'est-à-dire pas de roues du tout), un coffre manquant, une cabine faite d'un matériau inconnu, et surtout, avec un propriétaire indéfini. Tu peux facilement imaginer comment une telle erreur peut faire « dérailler » le programme en cours d'exécution ! Nous devons empêcher la survenue de telles situations d'une manière ou d'une autre. Nous devons restreindre notre programme : lors de la création d'un nouvel objet Car, nous voulons que les champs, comme le modèle et la vitesse maximale, soient toujours spécifiés. Sinon, nous empêchons la création de l'objet. Les constructeurs gèrent cette tâche avec facilité. Ils portent ce nom pour une bonne raison. Le constructeur en Java crée une sorte de « squelette » de classe auquel chaque nouvel objet doit se conformer. Pour plus de commodité, revenons à la version plus simple de la classe Car avec deux champs. Compte tenu de nos exigences, le constructeur de la classe Car ressemblera à ceci :
public Car(String model, int maxSpeed) {
   this.model = model;
   this.maxSpeed = maxSpeed;
}

// And creating an object now looks like this:

public static void main(String[] args) {
   Car bugatti = new Car("Bugatti Veyron", 378);
}

Le constructeur Java

Note comment un constructeur est déclaré. Il est similaire à une méthode ordinaire, mais il n'a pas de type de retour. En outre, le constructeur spécifie le nom de classe (Car) en commençant par une lettre majuscule. De plus, le constructeur Java est utilisé avec un mot-clé qui est nouveau pour toi : this. Le mot-clé this sert à indiquer un objet particulier. Le code du constructeur
public Car(String model, int maxSpeed) {
   this.model = model;
   this.maxSpeed = maxSpeed;
}
peut être interprété quasiment mot pour mot : « Le modèle de cette voiture (celle que nous créons maintenant) est l'argument model passé au constructeur. La vitesse maximale de cette voiture (celle que nous créons) est l'argument maxSpeed passé au constructeur. » Et c'est exactement ce qui se passe :
public class Car {

   String model;
   int maxSpeed;

   public Car(String model, int maxSpeed) {
       this.model = model;
       this.maxSpeed = maxSpeed;
   }

   public static void main(String[] args) {
       Car bugatti = new Car("Bugatti Veyron", 378);
       System.out.println(bugatti.model);
       System.out.println(bugatti.maxSpeed);
   }

}
Sortie de la console :
Bugatti Veyron 378
Le constructeur a bien affecté les valeurs requises. Tu as peut-être remarqué qu'un constructeur était très similaire à une méthode ordinaire ! Et tu as tout à fait raison. Un constructeur est vraiment une méthode, juste avec des fonctionnalités spécifiques :) Tout comme avec les méthodes, nous avons transmis des arguments à notre constructeur. Et tout comme pour l'appel d'une méthode, appeler un constructeur ne fonctionnera pas à moins que tu les spécifies :
public class Car {

   String model;
   int maxSpeed;

   public Car(String model, int maxSpeed) {
       this.model = model;
       this.maxSpeed = maxSpeed;
   }

   public static void main(String[] args) {
       Car bugatti = new Car(); // Error!
   }

}
Tu peux voir que le constructeur accomplit ce que nous essayions de faire. Maintenant, tu ne peux plus créer une voiture sans vitesse ou modèle ! La similitude entre les constructeurs et les méthodes ne s'arrête pas là. Tout comme avec les méthodes, les constructeurs peuvent être surchargés. Imagine que tu as 2 chats de compagnie à la maison. Tu en as un depuis qu'il est chaton. Mais tu as adopté le deuxième dans la rue quand il était déjà adulte, et tu ne sais pas exactement quel âge il a. Dans ce cas, nous voulons que notre programme soit en mesure de créer deux types de chats : ceux qui ont un nom et un âge (pour le premier chat), et ceux qui n'ont qu'un nom (pour le deuxième chat). Pour cela, nous surchargerons le constructeur :
public class Cat {

   String name;
   int age;

   // For the first cat
   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   // For the second cat
   public Cat(String name) {
       this.name = name;
   }

   public static void main(String[] args) {

       Cat smudge = new Cat("Smudge", 5);
       Cat streetCatNamedBob = new Cat("Bob");
   }

}
En plus du constructeur d'origine avec les paramètres « name » et « age », nous avons ajouté un autre constructeur avec seulement le paramètre name. Exactement de la même manière que nous avons surchargé les méthodes dans les leçons précédentes. Maintenant, nous pouvons créer les deux types de chats :) Tu te souviens du début de la leçon, quand nous avons dit que tu avais déjà utilisé des constructeurs sans t'en rendre compte ? Nous ne plaisantions pas. Le fait est que chaque classe en Java possède ce qu'on appelle un constructeur par défaut. Il ne prend pas d'arguments, mais il est appelé chaque fois que tu crées un objet de n'importe quelle classe.
public class Cat {

   public static void main(String[] args) {

       Cat smudge = new Cat(); // The default constructor is invoked here
   }
}
À première vue, il est invisible. Nous avons créé un objet, et après ? Où le constructeur a-t-il fait quelque chose ici ? Pour le voir, écrivons explicitement un constructeur vide pour la classe Cat. Nous allons afficher une phrase à l'intérieur. Si la phrase s'affiche, c'est que le constructeur a été appelé.
public class Cat {

   public Cat() {
       System.out.println("A cat has been created!");
   }

   public static void main(String[] args) {

       Cat smudge = new Cat(); // The default constructor is invoked here
   }
}
Sortie de la console :
Un chat a été créé !
Voilà la confirmation ! Le constructeur par défaut est toujours présent dans tes classes, même s'il est invisible. Mais tu dois savoir une chose de plus à ce sujet. Le constructeur par défaut est éliminé d'une classe une fois que tu crées un constructeur avec des arguments. En fait, nous en avons déjà eu la preuve plus haut. C'était dans ce code :
public class Cat {

   String name;
   int age;

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

   public static void main(String[] args) {

       Cat smudge = new Cat(); //Error!
   }
}
Nous n'avons pas pu créer de Cat sans nom et âge, car nous avons déclaré un constructeur Cat avec des paramètres string et int. Cela a causé la disparition immédiate du constructeur par défaut de la classe. N'oublie donc pas que si tu as besoin de plusieurs constructeurs dans ta classe, dont un constructeur sans argument, tu devras déclarer ce dernier séparément. Par exemple, supposons que nous créons un programme pour une clinique vétérinaire. Notre clinique veut faire de bonnes actions et aider les chatons sans-abri dont les noms et âges sont inconnus. Dans ce cas, notre code ressemblerait à ceci :
public class Cat {

   String name;
   int age;

   // For cats with owners
   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   // For street cats
   public Cat() {
   }

   public static void main(String[] args) {

       Cat smudge = new Cat("Smudge", 5);
       Cat streetCat = new Cat();
   }
}
Maintenant que nous avons écrit un constructeur par défaut explicite, nous pouvons créer les deux types de chats :) Comme avec toute méthode, l'ordre de transmission des arguments à un constructeur est très important. Échangeons les arguments name et age dans notre constructeur.
public class Cat {

   String name;
   int age;

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

   public static void main(String[] args) {

       Cat smudge = new Cat("Smudge", 10); // Error!
   }
}
Une erreur ! Le constructeur stipule clairement que lorsqu'un objet Cat est créé, il doit recevoir un nombre et une chaîne, dans cet ordre. Donc notre code ne fonctionne pas. Essaie de t'en souvenir et de le garder à l'esprit lorsque tu déclareras tes propres classes :
public Cat(String name, int age) {
   this.name = name;
   this.age = age;
}

public Cat(int age, String name) {
   this.age = age;
   this.name = name;
}
Ce sont deux constructeurs totalement différents ! Si nous devions exprimer en une seule phrase la réponse à la question « Pourquoi ai-je besoin d'un constructeur ? », nous pourrions dire : « Pour s'assurer que les objets ont toujours un état valide ». Lorsque tu utilises des constructeurs, toutes tes variables sont correctement initialisées. Tes programmes n'auront pas de voitures avec une vitesse de 0 ou d'autres objets « non valides ». Leur principal avantage est pour le programmeur. Si tu initialises les champs manuellement (après la création d'un objet), il y a un grand risque que tu rates quelque chose et introduises un bogue. Mais cela ne se produira pas avec un constructeur : si tu oublies de transmettre des arguments requis ou si tu passes les mauvais types d'arguments, le compilateur enregistrera immédiatement une erreur. Nous devrions également noter au passage que tu ne dois pas placer la logique de ton programme à l'intérieur d'un constructeur. C'est à cela que les méthodes servent. Les méthodes sont l'endroit où tu dois définir toutes les fonctionnalités requises. Voyons pourquoi ajouter la logique à un constructeur est une mauvaise idée :
public class CarFactory {

   String name;
   int age;
   int carsCount;

   public CarFactory(String name, int age, int carsCount) {
   this.name = name;
   this.age = age;
   this.carsCount = carsCount;

   System.out.println("Our car factory is called " + this.name);
   System.out.println("It was founded " + this.age + " years ago" );
   System.out.println("Since that time, it has produced " + this.carsCount +  " cars");
   System.out.println("On average, it produces " + (this.carsCount/this.age) + " cars per year");
}

   public static void main(String[] args) {

       CarFactory ford = new CarFactory("Ford", 115 , 50000000);
   }
}
Nous avons une classe CarFactory qui décrit l'usine de voitures. À l'intérieur du constructeur, nous initialisons tous les champs et incluons une certaine logique : nous affichons quelques informations sur l'usine. A priori rien de mal à cela. Le programme fonctionne. Sortie de la console :
Notre usine automobile s'appelle Ford Elle a été fondée il y a 115 ans Depuis, elle a produit 50000000 voitures En moyenne, elle produit 434782 voitures par an
Mais nous avons posé une bombe à retardement sans le réaliser. Et ce genre de code peut très facilement conduire à des erreurs. Supposons que maintenant nous ne parlons pas de Ford, mais d'une nouvelle usine appelée « Amigo Motors », qui existe depuis moins d'un an et a produit 1000 voitures :
public class CarFactory {

   String name;
   int age;
   int carsCount;

   public CarFactory(String name, int age, int carsCount) {
   this.name = name;
   this.age = age;
   this.carsCount = carsCount;

   System.out.println("Our car factor is called " + this.name);
   System.out.println("It was founded " + this.age + " years ago" );
   System.out.println("Since that time, it has produced " + this.carsCount +  " cars");
   System.out.println("On average, it produces " + (this.carsCount/this.age) + " cars per year");
}


   public static void main(String[] args) {

       CarFactory ford = new CarFactory("Amigo Motors", 0 , 1000);
   }
}
Sortie de la console :
Notre usine de voitures s'appelle Amigo Motors Exception in thread "main" java.lang.ArithmeticException: / by zero Elle a été fondée il y a 0 ans Depuis, elle a produit 1000 voitures at CarFactory.(CarFactory.java:15) at CarFactory.main(CarFactory.java:23) Process finished with exit code 1
Boum ! Le programme se termine par une sorte d'erreur incompréhensible. Tu peux en deviner la cause ? Le problème est dans la logique que nous avons placée dans le constructeur. Plus précisément, cette ligne :
System.out.println("On average, it produces " + (this.carsCount/this.age) + " cars per year");
Ici, tu effectues un calcul et divises le nombre de voitures produites par l'âge de l'usine. Et comme notre usine est nouvelle (c'est-à-dire qu'elle a 0 an), nous divisons par 0, ce que nous ne pouvons pas faire en mathématiques. Par conséquent, le programme se termine sur une erreur. Qu'aurions-nous dû faire ? Mettre toute la logique dans une méthode distincte. Appelons cette méthode printFactoryInfo(). Tu peux lui transmettre un objet CarFactory comme argument. Tu peux y mettre toute la logique et gérer les erreurs potentielles au passage (comme la nôtre impliquant un nombre d'années nul). À chacun son rôle. Les constructeurs sont nécessaires pour définir un état d'objet valide. Nous avons les méthodes pour la logique. Ne mélange pas l'un avec l'autre.
Cet article est également disponible en anglais:
Read the English version of this article to understand constructors in Java. Constructors are more interesting and subtle than you may realize.
Commentaires
  • Populaires
  • Nouveau
  • Anciennes
Tu dois être connecté(e) pour laisser un commentaire
Cette page ne comporte pas encore de commentaires