Les constructeurs Java
Initialisez vos objets Java avec des constructeurs — par défaut, paramétrés, surchargés, et enchaînement de constructeurs.
Un constructeur est une méthode spéciale qui s'exécute une fois lorsqu'un objet est créé. Son rôle est de faire passer la nouvelle instance de « tous les champs à leur valeur par défaut » à « prête à l'emploi ». Jusqu'à présent, vous définissiez les champs un par un après new Dog() ; un constructeur vous permet de regrouper ce travail dans l'appel à new.
Anatomie d'un constructeur
Un constructeur ressemble à une méthode, avec trois différences : il porte le même nom que la classe, il n'a pas de type de retour (pas même void), et il est appelé uniquement par new :
public class Point {
int x, y;
public Point(int x, int y) { // constructor
this.x = x;
this.y = y;
}
}
Point p = new Point(3, 4); // runs the constructorLa liste d'arguments se place entre les () après le nom de la classe dans l'expression new. Java fait correspondre cette liste d'arguments à un constructeur de la classe, tout comme il fait correspondre un appel de méthode à une méthode.
Le constructeur par défaut
Si vous ne déclarez aucun constructeur, Java fournit à la classe un constructeur par défaut — un constructeur sans argument qui ne fait rien :
public class Empty { } // compiler generates a no-arg constructor
Empty e = new Empty(); // worksC'est ainsi que new Dog() fonctionnait dans les exemples précédents même si nous n'avions jamais écrit public Dog() { ... }. Dès que vous déclarez n'importe quel constructeur, le constructeur par défaut gratuit disparaît :
public class Point {
int x, y;
public Point(int x, int y) { this.x = x; this.y = y; }
}
Point p = new Point(); // ERROR — no no-arg constructor existsSi vous voulez les deux, déclarez les deux — voir la surcharge ci-dessous.
Pourquoi utiliser un constructeur ?
Deux raisons.
1. Les champs obligatoires. Un constructeur vous permet de rendre certains champs non optionnels. Avec l'approche champ public puis affectation, rien n'empêche un appelant de créer un Person sans name. Avec un constructeur Person(String name), c'est impossible.
2. Les invariants. Un constructeur peut valider ses arguments avant que l'objet n'existe sous une forme utilisable. Si un Circle exige un rayon positif, le constructeur peut lever une exception sur une valeur négative — l'objet invalide n'est jamais créé.
public class Circle {
double radius;
public Circle(double radius) {
if (radius <= 0) throw new IllegalArgumentException("radius must be > 0");
this.radius = radius;
}
}L'appelant ne peut désormais pas obtenir un Circle avec un rayon non positif.
Les constructeurs surchargés
Une classe peut avoir plusieurs constructeurs avec des listes de paramètres différentes, exactement comme des méthodes surchargées :
public class Rectangle {
double width, height;
public Rectangle() { this(1, 1); }
public Rectangle(double side) { this(side, side); } // a square
public Rectangle(double w, double h) { this.width = w; this.height = h; }
}Ces trois appels compilent maintenant :
Rectangle a = new Rectangle(); // 1 x 1
Rectangle b = new Rectangle(5); // 5 x 5
Rectangle c = new Rectangle(3, 4); // 3 x 4Les constructeurs de copie
Une surcharge courante est le constructeur de copie — un constructeur qui prend une autre instance de la même classe et en crée une copie indépendante. Java n'a pas de constructeur de copie intégré (contrairement à C++), vous devez donc écrire le vôtre lorsque vous en avez besoin :
public class Point {
int x, y;
public Point(int x, int y) { this.x = x; this.y = y; }
public Point(Point other) { this(other.x, other.y); } // copy constructor
}
Point a = new Point(3, 4);
Point b = new Point(a); // a separate Point with the same valuesComme b est un nouvel objet, modifier b.x par la suite n'affecte pas a. Pour les classes dont les champs sont eux-mêmes des objets mutables, copiez également les champs (une copie profonde) si vous souhaitez que la copie soit totalement indépendante.
this(...) — l'enchaînement de constructeurs
À l'intérieur d'un constructeur, this(args) appelle un autre constructeur de la même classe. C'est ainsi que l'exemple Rectangle ci-dessus évitait de dupliquer le code d'affectation des champs : les deux constructeurs de commodité délèguent au constructeur complet.
Deux règles :
this(...)doit être la première instruction du corps du constructeur.- Un constructeur peut s'enchaîner à un seul autre constructeur.
public Rectangle() { this(1, 1); } // ok
public Rectangle(double s) { System.out.println("hi"); this(s, s); } // ERRORLa raison de la règle « première instruction » est que la JVM doit initialiser complètement l'objet exactement une fois avant l'exécution de tout autre code.
super(...) — appeler le parent
Lorsqu'une classe en étend une autre, chaque constructeur appelle soit explicitement un constructeur parent avec super(args), soit implicitement le constructeur sans argument du parent. Nous couvrirons cela en détail dans les chapitres héritage et mot-clé super ; pour l'instant, sachez simplement que super(...) existe et obéit à la même règle de première instruction que this(...).
Les constructeurs ne peuvent pas retourner de valeur
Un constructeur n'a pas de type de retour — pas même void. Si vous en écrivez un :
public void Point(int x, int y) { ... } // not a constructor!…c'est une méthode ordinaire qui se trouve à s'appeler Point. Le compilateur ne vous avertira pas ; new Point(3, 4) échouera alors à la compilation car le vrai constructeur qu'il recherche n'existe pas. C'est une faute de frappe étonnamment courante.
L'ordre d'initialisation
Pour une seule classe, l'ordre est le suivant :
- Les valeurs par défaut des champs sont affectées (par exemple, les champs
intvalent0). - Les initialiseurs de champs en ligne (
int count = 5;) et les éventuels blocs d'initialisation d'instance s'exécutent dans l'ordre où ils apparaissent. - Le corps du constructeur s'exécute.
public class Demo {
int a = compute("a-init", 1);
int b;
{ b = compute("b-block", 2); } // instance initializer
public Demo() {
System.out.println("constructor; a=" + a + ", b=" + b);
}
static int compute(String label, int v) {
System.out.println(label);
return v;
}
}new Demo() affiche a-init, b-block, puis constructor; a=1, b=2.
En pratique, presque toute l'initialisation appartient au corps du constructeur. Les blocs d'initialisation d'instance sont rares dans le code réel ; ils existent principalement pour des situations comme les classes anonymes (traitées plus tard).
Un exemple complet
La suite
Dans les constructeurs, vous avez déjà vu this.field = field pour distinguer un paramètre d'un champ. Le chapitre sur le mot-clé this précise ce qu'est this et les quelques endroits où vous devez l'écrire explicitement.