Comme vous l’avez appris dans le Chapitre 1, un constructeur est une méthode spéciale qui correspond au nom de la classe et n’a pas de type de retour. Il est appelé lorsqu’une nouvelle instance de la classe est créée. Dans cette section, nous montrons comment créer un constructeur. Ensuite, nous examinons les constructeurs par défaut, la surcharge de constructeurs, l’appel des constructeurs parents, les champs final
, et l’ordre d’initialisation dans une classe.
Créer un Constructeur
Commençons par un constructeur simple :
public class Lapin {
public Lapin() {
System.out.print("saut");
}
}
Le nom du constructeur, Lapin
, correspond au nom de la classe, Lapin
, et il n’y a pas de type de retour, pas même void
. Cela en fait un constructeur. Pouvez-vous dire pourquoi ces deux ne sont pas des constructeurs valides pour la classe Lapin
?
public class Lapin {
public lapin() {} // NE COMPILE PAS
public void Lapin() {}
}
Le premier ne correspond pas au nom de la classe car Java est sensible à la casse. Comme il ne correspond pas, Java sait qu’il ne peut pas être un constructeur et est censé être une méthode régulière. Cependant, il manque le type de retour et ne compile pas. La deuxième méthode est une méthode parfaitement correcte mais n’est pas un constructeur car elle a un type de retour.
Comme les paramètres de méthode, les paramètres de constructeur peuvent être n’importe quelle classe valide, tableau ou type primitif, y compris les génériques, mais ne peuvent pas inclure var
. Par exemple, ce qui suit ne compile pas :
public class Bonobo {
public Bonobo(var nourriture) { // NE COMPILE PAS
}
}
Une classe peut avoir plusieurs constructeurs, tant que chaque constructeur a une signature unique. Dans ce cas, cela signifie que les paramètres du constructeur doivent être distincts. Comme les méthodes avec le même nom mais des signatures différentes, déclarer plusieurs constructeurs avec des signatures différentes est appelé surcharge de constructeur. La classe Tortue
suivante a quatre constructeurs surchargés distincts :
public class Tortue {
private String nom;
public Tortue() {
nom = "Jean Dupont";
}
public Tortue(int age) {}
public Tortue(long age) {}
public Tortue(String nouveauNom, String... alimentsPreferees) {
nom = nouveauNom;
}
}
Les constructeurs sont utilisés lors de la création d’un nouvel objet. Ce processus est appelé instanciation car il crée une nouvelle instance de la classe. Un constructeur est appelé lorsque nous écrivons new
suivi du nom de la classe que nous voulons instancier. Voici un exemple :
new Tortue(15)
Lorsque Java voit le mot-clé new
, il alloue de la mémoire pour le nouvel objet. Il recherche ensuite un constructeur avec une signature correspondante et l’appelle.
Le Constructeur par Défaut
Chaque classe en Java a un constructeur, que vous en codiez un ou non. Si vous n’incluez aucun constructeur dans la classe, Java en créera un pour vous sans paramètres. Ce constructeur créé par Java est appelé le constructeur par défaut et est ajouté chaque fois qu’une classe est déclarée sans constructeurs. Nous l’appelons souvent le constructeur par défaut sans argument, pour plus de clarté. Voici un exemple :
public class Lapin {
public static void main(String[] args) {
new Lapin(); // Appelle le constructeur par défaut
}
}
Dans la classe Lapin
, Java voit qu’aucun constructeur n’a été codé et en crée un. La classe précédente est équivalente à la suivante, dans laquelle le constructeur par défaut est fourni et donc non inséré par le compilateur :
public class Lapin {
public Lapin() {}
public static void main(String[] args) {
new Lapin(); // Appelle le constructeur défini par l'utilisateur
}
}
Le constructeur par défaut a une liste de paramètres vide et un corps vide. Vous pouvez tout à fait l’écrire vous-même. Cependant, comme il ne fait rien, Java est heureux de le générer pour vous et vous épargner un peu de frappe.
Nous disons souvent généré. Cela se produit pendant l’étape de compilation. Si vous regardez le fichier avec l’extension .java
, le constructeur sera toujours manquant. Il n’apparaît que dans le fichier compilé avec l’extension .class
.
Une des règles les plus importantes que vous devez connaître est que le compilateur insère le constructeur par défaut uniquement lorsqu’aucun constructeur n’est défini. Laquelle de ces classes pensez-vous avoir un constructeur par défaut ?
public class Lapin1 {}
public class Lapin2 {
public Lapin2() {}
}
public class Lapin3 {
public Lapin3(boolean b) {}
}
public class Lapin4 {
private Lapin4() {}
}
Seul Lapin1
obtient un constructeur par défaut sans argument. Il n’a pas de constructeur codé, donc Java génère un constructeur par défaut sans argument. Lapin2
et Lapin3
ont tous deux des constructeurs public
déjà définis. Lapin4
a un constructeur private
. Comme ces trois classes ont un constructeur défini, le constructeur par défaut sans argument n’est pas inséré pour vous.
Jetons un coup d’œil rapide sur la façon d’appeler ces constructeurs :
public class LapinsMultiplient {
public static void main(String[] args) {
var l1 = new Lapin1();
var l2 = new Lapin2();
var l3 = new Lapin3(true);
var l4 = new Lapin4(); // NE COMPILE PAS
}
}
La ligne 3 appelle le constructeur par défaut sans argument généré. Les lignes 4 et 5 appellent les constructeurs fournis par l’utilisateur. La ligne 6 ne compile pas. Lapin4
a rendu le constructeur private
pour que les autres classes ne puissent pas l’appeler.
Avoir uniquement des constructeurs private
dans une classe indique au compilateur de ne pas fournir de constructeur par défaut sans argument. Cela empêche également les autres classes d’instancier la classe. C’est utile lorsqu’une classe n’a que des méthodes static
ou lorsque le développeur veut avoir un contrôle total sur tous les appels pour créer de nouvelles instances de la classe.
Appeler des Constructeurs Surchargés avec this()
Maîtrisez-vous les bases sur la création et la référence des constructeurs ? Très bien, car les choses vont devenir un peu plus compliquées. Puisqu’une classe peut contenir plusieurs constructeurs surchargés, ces constructeurs peuvent en fait s’appeler les uns les autres. Commençons par une classe simple contenant deux constructeurs surchargés :
public class Hamster {
private String couleur;
private int poids;
public Hamster(int poids, String couleur) { // Premier constructeur
this.poids = poids;
this.couleur = couleur;
}
public Hamster(int poids) { // Deuxième constructeur
this.poids = poids;
couleur = "marron";
}
}
L’un des constructeurs prend un seul paramètre int
. L’autre prend un int
et une String
. Ces listes de paramètres sont différentes, donc les constructeurs sont correctement surchargés.
Il y a un peu de duplication, car this.poids
est assigné de la même manière dans les deux constructeurs. En programmation, même un peu de duplication a tendance à se transformer en beaucoup de duplication à mesure que nous ajoutons “juste une chose de plus”. Par exemple, imaginez que nous ayons cinq variables définies comme this.poids
, plutôt qu’une seule. Ce que nous voulons vraiment, c’est que le premier constructeur appelle le deuxième constructeur avec deux paramètres. Alors, comment pouvons-nous faire en sorte qu’un constructeur en appelle un autre ? Vous pourriez être tenté de réécrire le premier constructeur comme suit :
public Hamster(int poids) { // Deuxième constructeur
Hamster(poids, "marron"); // NE COMPILE PAS
}
Cela ne fonctionnera pas. Les constructeurs ne peuvent être appelés qu’en écrivant new
avant le nom du constructeur. Ils ne sont pas comme des méthodes normales que vous pouvez simplement appeler. Que se passe-t-il si nous mettons new
avant le nom du constructeur ?
public Hamster(int poids) { // Deuxième constructeur
new Hamster(poids, "marron"); // Compile, mais crée un objet supplémentaire
}
Cette tentative compile. Cependant, elle ne fait pas ce que nous voulons. Lorsque ce constructeur est appelé, il crée un nouvel objet avec le poids et la couleur par défaut. Il construit ensuite un objet différent avec le poids et la couleur souhaités. De cette manière, nous nous retrouvons avec deux objets, dont l’un est jeté après sa création. Ce n’est pas ce que nous voulons. Nous voulons que le poids et la couleur soient définis sur l’objet que nous essayons d’instancier en premier lieu.
Java fournit une solution : this()
— oui, le même mot-clé que nous avons utilisé pour faire référence aux membres d’instance, mais avec des parenthèses. Lorsque this()
est utilisé avec des parenthèses, Java appelle un autre constructeur sur la même instance de la classe.
public Hamster(int poids) { // Deuxième constructeur
this(poids, "marron");
}
Succès ! Maintenant, Java appelle le constructeur qui prend deux paramètres, avec le poids et la couleur définis comme prévu.
this vs. this()
Bien qu’utilisant le même mot-clé, this
et this()
sont très différents. Le premier, this
, fait référence à une instance de la classe, tandis que le second, this()
, fait référence à un appel de constructeur dans la classe. L’examen peut essayer de vous piéger en utilisant les deux ensemble, alors assurez-vous de savoir lequel utiliser et pourquoi.
L’appel de this()
a une règle spéciale que vous devez connaître. Si vous choisissez de l’appeler, l’appel this()
doit être la première instruction dans le constructeur. L’effet secondaire de ceci est qu’il ne peut y avoir qu’un seul appel à this()
dans n’importe quel constructeur.
public Hamster(int poids) {
System.out.println("mâche");
// Définir le poids et la couleur par défaut
this(poids, "marron"); // NE COMPILE PAS
}
Même si une instruction d’impression ne change aucune variable, c’est toujours une instruction Java et n’est pas autorisée à être insérée avant l’appel à this()
. Le commentaire est tout à fait correct. Les commentaires ne sont pas considérés comme des instructions et sont autorisés n’importe où.
Il y a une dernière règle pour les constructeurs surchargés dont vous devriez être conscient. Considérez la définition suivante de la classe Gaufre
:
public class Gaufre {
public Gaufre(int trousCreuses) {
this(5); // NE COMPILE PAS
}
}
Le compilateur est capable de détecter que ce constructeur s’appelle lui-même infiniment. C’est souvent appelé un cycle et est similaire aux boucles infinies que nous avons discutées au Chapitre 3, “Prendre des Décisions”. Comme le code ne peut jamais se terminer, le compilateur s’arrête et signale ceci comme une erreur. De même, ceci ne compile pas non plus :
public class Gaufre {
public Gaufre() {
this(5); // NE COMPILE PAS
}
public Gaufre(int trousCreuses) {
this(); // NE COMPILE PAS
}
}
Dans cet exemple, les constructeurs s’appellent l’un l’autre, et le processus continue infiniment. Comme le compilateur peut le détecter, il signale une erreur.
Voici un résumé des règles que vous devriez connaître sur les constructeurs que nous avons couvertes dans cette section. Étudiez-les bien !
- Une classe peut contenir de nombreux constructeurs surchargés, à condition que la signature de chacun soit distincte.
- Le compilateur insère un constructeur par défaut sans argument si aucun constructeur n’est déclaré.
- Si un constructeur appelle
this()
, alors ce doit être la première ligne du constructeur. - Java n’autorise pas les appels cycliques de constructeurs.
Appeler les Constructeurs Parents avec super()
Félicitations : vous êtes en bonne voie pour devenir un expert dans l’utilisation des constructeurs ! Il y a encore un ensemble de règles que nous devons couvrir, cependant, pour appeler les constructeurs dans la classe parent. Après tout, comment les membres d’instance de la classe parent sont-ils initialisés ?
La première instruction de chaque constructeur est un appel à un constructeur parent en utilisant super()
ou à un autre constructeur surchargé en utilisant this()
. Lisez la phrase précédente deux fois pour vous assurer de vous en souvenir. C’est vraiment important !
Pour simplifier dans cette section, nous faisons souvent référence à super()
et this()
pour désigner n’importe quel appel de constructeur parent ou surchargé, même ceux qui prennent des arguments.
Jetons un coup d’œil à la classe Animal
et à sa sous-classe Zebre
et voyons comment leurs constructeurs peuvent être correctement écrits pour s’appeler les uns les autres :
public class Animal {
private int age;
public Animal(int age) {
super(); // Fait référence au constructeur dans java.lang.Object
this.age = age;
}
}
public class Zebre extends Animal {
public Zebre(int age) {
super(age); // Fait référence au constructeur dans Animal
}
public Zebre() {
this(4); // Fait référence au constructeur dans Zebre avec argument int
}
}
Dans la classe Animal
, la première instruction du constructeur est un appel au constructeur parent défini dans java.lang.Object
, qui ne prend pas d’arguments. Dans la deuxième classe, Zebre
, la première instruction du premier constructeur est un appel au constructeur d’Animal
, qui prend un seul argument. La classe Zebre
inclut également un deuxième constructeur sans argument qui n’appelle pas super()
mais appelle plutôt l’autre constructeur dans la classe Zebre
en utilisant this(4)
.
super vs. super()
Comme this
et this()
, super
et super()
sont sans rapport en Java. Le premier, super
, est utilisé pour référencer les membres de la classe parent, tandis que le second, super()
, appelle un constructeur parent. Chaque fois que vous voyez le mot-clé super
lors de l’examen, assurez-vous qu’il est utilisé correctement.
Comme l’appel de this()
, l’appel de super()
ne peut être utilisé que comme première instruction du constructeur. Par exemple, les deux définitions de classe suivantes ne compileront pas :
public class Zoo {
public Zoo() {
System.out.println("Zoo créé");
super(); // NE COMPILE PAS
}
}
public class Zoo {
public Zoo() {
super();
System.out.println("Zoo créé");
super(); // NE COMPILE PAS
}
}
La première classe ne compilera pas car l’appel au constructeur parent doit être la première instruction du constructeur. Dans le deuxième extrait de code, super()
est la première instruction du constructeur, mais il est également utilisé comme troisième instruction. Comme super()
ne peut être appelé qu’une seule fois comme première instruction du constructeur, le code ne compilera pas.
Si la classe parent a plus d’un constructeur, la classe enfant peut utiliser n’importe quel constructeur parent valide dans sa définition, comme le montre l’exemple suivant :
public class Animal {
private int age;
private String nom;
public Animal(int age, String nom) {
super();
this.age = age;
this.nom = nom;
}
public Animal(int age) {
super();
this.age = age;
this.nom = null;
}
}
public class Gorille extends Animal {
public Gorille(int age) {
super(age, "Gorille"); // Appelle le premier constructeur Animal
}
public Gorille() {
super(5); // Appelle le deuxième constructeur Animal
}
}
Dans cet exemple, le premier constructeur enfant prend un argument, age
, et appelle le constructeur parent, qui prend deux arguments, age
et nom
. Le deuxième constructeur enfant ne prend pas d’arguments, et il appelle le constructeur parent, qui prend un argument, age
. Dans cet exemple, notez que les constructeurs enfants ne sont pas obligés d’appeler des constructeurs parents correspondants. N’importe quel constructeur parent valide est acceptable tant que les paramètres d’entrée appropriés au constructeur parent sont fournis.
Comprendre les Améliorations du Compilateur
Attendez une seconde : nous avons dit que la première ligne de chaque constructeur est un appel à this()
ou super()
, mais nous avons créé des classes et des constructeurs tout au long de ce livre, et nous l’avons rarement fait. Comment ces classes ont-elles compilé ?
La réponse est que le compilateur Java insère automatiquement un appel au constructeur sans argument super()
si vous n’appelez pas explicitement this()
ou super()
comme première ligne d’un constructeur. Par exemple, les trois définitions de classe et de constructeur suivantes sont équivalentes, car le compilateur les convertira automatiquement toutes en la dernière exemple :
public class Ane {}
public class Ane {
public Ane() {}
}
public class Ane {
public Ane() {
super();
}
}
Assurez-vous de comprendre les différences entre ces trois définitions de classe Ane
et pourquoi Java les convertira automatiquement toutes en la dernière définition. En lisant la section suivante, gardez à l’esprit le processus que le compilateur Java effectue.
Astuces et Astuces pour le Constructeur par Défaut
Nous avons présenté beaucoup de règles jusqu’à présent, et vous avez peut-être remarqué quelque chose. Disons que nous avons une classe qui n’inclut pas de constructeur sans argument. Que se passe-t-il si nous définissons une sous-classe sans constructeurs, ou une sous-classe avec un constructeur qui n’inclut pas de référence super()
?
public class Mammifere {
public Mammifere(int age) {}
}
public class Phoque extends Mammifere {} // NE COMPILE PAS
public class Elephant extends Mammifere {
public Elephant() {} // NE COMPILE PAS
}
La réponse est qu’aucune des sous-classes ne compile. Puisque Mammifere
définit un constructeur, le compilateur n’insère pas de constructeur sans argument. Le compilateur insérera un constructeur par défaut sans argument dans Phoque
, cependant, mais ce sera une implémentation simple qui appelle simplement un constructeur parent par défaut inexistant.
public class Phoque extends Mammifere {
public Phoque() {
super(); // NE COMPILE PAS
}
}
De même, Elephant
ne compilera pas pour des raisons similaires. Le compilateur ne voit pas d’appel à super()
ou this()
comme première ligne du constructeur, donc il insère automatiquement un appel à un super()
sans argument inexistant.
public class Elephant extends Mammifere {
public Elephant() {
super(); // NE COMPILE PAS
}
}
Dans ces cas, le compilateur ne vous aidera pas, et vous devez créer au moins un constructeur dans votre classe enfant qui appelle explicitement un constructeur parent via la commande super()
.
public class Phoque extends Mammifere {
public Phoque() {
super(6); // Appel explicite au constructeur parent
}
}
public class Elephant extends Mammifere {
public Elephant() {
super(4); // Appel explicite au constructeur parent
}
}
Les sous-classes peuvent inclure des constructeurs sans argument même si leurs classes parents n’en ont pas. Par exemple, ce qui suit compile car Elephant
inclut un constructeur sans argument :
public class ElephantAfricain extends Elephant {}
C’est beaucoup à assimiler, nous le savons. Vous devriez être capable de repérer immédiatement pourquoi des classes comme nos premières implémentations de Phoque
et Elephant
n’ont pas compilé.
super() Fait Toujours Référence au Parent le Plus Direct
Une classe peut avoir plusieurs ancêtres via l’héritage. Dans notre exemple précédent, ElephantAfricain
est une sous-classe d’Elephant
, qui à son tour est une sous-classe de Mammifere
. Pour les constructeurs, cependant, super()
fait toujours référence au parent le plus direct. Dans cet exemple, l’appel de super()
à l’intérieur de la classe ElephantAfricain
fait toujours référence à la classe Elephant
et jamais à la classe Mammifere
.
Nous concluons cette section en ajoutant trois règles de constructeur à votre ensemble de compétences :
- La première ligne de chaque constructeur est un appel à un constructeur parent en utilisant
super()
ou à un constructeur surchargé en utilisantthis()
. - Si le constructeur ne contient pas de référence
this()
ousuper()
, alors le compilateur insère automatiquementsuper()
sans arguments comme première ligne du constructeur. - Si un constructeur appelle
super()
, alors ce doit être la première ligne du constructeur.
Félicitations : vous avez appris tout ce que nous pouvons vous enseigner sur la déclaration des constructeurs. Ensuite, nous passons à l’initialisation et discutons de la façon d’utiliser les constructeurs.