Initialisation du constructeur par défaut et du champ en ligne

Quelle est la différence entre un constructeur par défaut et l’initialisation directe des champs d’un object directement?

Quelles sont les raisons de préférer l’un des exemples suivants à l’autre?

Exemple 1

public class Foo { private int x = 5; private Ssortingng[] y = new Ssortingng[10]; } 

Exemple 2

 public class Foo { private int x; private Ssortingng[] y; public Foo() { x = 5; y = new Ssortingng[10]; } } 

Les initialiseurs sont exécutés avant les corps de constructeurs. (Ce qui a des implications si vous avez à la fois des initialiseurs et des constructeurs, le code constructeur s’exécute en second et remplace une valeur initialisée)

Les initialiseurs sont bons lorsque vous avez toujours besoin de la même valeur initiale (comme dans votre exemple, un tableau de taille donnée ou un nombre entier de valeur spécifique), mais cela peut fonctionner en votre faveur ou contre vous:

Si vous avez beaucoup de constructeurs qui initialisent les variables différemment (c’est-à-dire avec des valeurs différentes), les initialiseurs sont inutiles car les modifications seront remplacées et inutiles.

D’autre part, si vous avez plusieurs constructeurs qui s’initialisent avec la même valeur, vous pouvez enregistrer des lignes de code (et rendre votre code légèrement plus maintenable) en conservant l’initialisation au même endroit.

Comme Michael l’a dit, il y a aussi une question de goût – vous aimeriez peut-être garder le code au même endroit. Bien que si vous avez beaucoup de constructeurs, votre code ne se trouve en aucun cas dans un endroit, donc je privilégierais les initialiseurs.

La raison de préférer l’exemple 1 est que c’est la même fonctionnalité pour moins de code (ce qui est toujours bon).

En dehors de cela, pas de différence.

Cependant, si vous avez des constructeurs explicites, je préférerais mettre tout le code d’initialisation dans ceux-ci (et les enchaîner) plutôt que de le séparer entre les constructeurs et les initialiseurs de champs.

Je préfère les initialiseurs de champs et j’ai recours à un constructeur par défaut quand il y a une logique d’initialisation complexe à effectuer (par exemple, remplir une carte, une ivar en dépend par une série d’étapes heuristiques à exécuter, etc.).

@Michael B a dit:

… Je préférerais mettre tout le code d’initialisation dans ceux-ci (et les enchaîner) plutôt que de les séparer entre les constructeurs et les initialiseurs de champs.

MichaelB (je m’incline devant le représentant de 71+ K) est parfaitement logique, mais ma tendance est de conserver les initialisations simples dans les initialiseurs finaux en ligne et de faire la partie complexe des initialisations dans le constructeur.

Doit-on privilégier l’initialiseur de champs ou le constructeur pour donner une valeur par défaut à un champ?

Je ne considérerai pas les exceptions pouvant survenir lors de l’instanciation de champs et de l’instanciation paresseuse / avide de champs qui touchent d’autres préoccupations que les problèmes de lisibilité et de maintenabilité.
Pour deux codes exécutant la même logique et produisant le même résultat, il convient de privilégier la meilleure lisibilité et la meilleure maintenabilité.

TL; DR

  • choisir la première ou la deuxième option est avant tout une question d’ organisation du code, de lisibilité et de maintenabilité .

  • garder une cohérence dans la manière de choisir

  • n’hésitez pas à utiliser des initialiseurs de champs pour instancier des champs de Collection afin d’éviter une NullPointerException

  • n’utilisez pas les initialiseurs de champs pour les champs pouvant être écrasés par les constructeurs

  • dans les classes avec un seul constructeur, le moyen d’initialisation du champ est généralement plus lisible et moins verbeux

  • dans les classes à constructeurs multiples où les constructeurs n’ont que peu ou pas de couplage entre eux , le moyen d’initialisation du champ est généralement plus lisible et moins verbeux

  • dans les classes à constructeurs multiples où les constructeurs ont un couplage entre eux , aucune des deux méthodes n’est vraiment meilleure

  • dans les classes avec plusieurs constructeurs où les classes déclarent beaucoup de champs , comme les champs avec des valeurs par défaut peuvent être dispersés, les valoriser dans un constructeur central peut sembler intéressant


OP Question

Avec un code très simple, l’affectation pendant la déclaration de champ semble meilleure et c’est le cas.

Ceci est moins verbeux et plus droit:

 public class Foo { private int x = 5; private Ssortingng[] y = new Ssortingng[10]; } 

que la manière du constructeur:

 public class Foo{ private int x; private Ssortingng[] y; public Foo(){ x = 5; y = new Ssortingng[10]; } } 

Dans les classes réelles avec des spécificités si réelles, les choses sont différentes.
En fait, en fonction des spécificités rencontrées, il convient de privilégier un moyen, l’autre ou l’un d’entre eux.


Des exemples plus élaborés pour illustrer

Cas d’étude 1

Je commencerai par une simple classe de Car que je mettrai à jour pour illustrer ces points.
Car déclare 4 champs et quelques constructeurs qui ont une relation entre eux.

1.Donner une valeur par défaut dans les champs de saisie pour tous les champs est indésirable

 public class Car { private Ssortingng name = "Super car"; private Ssortingng origin = "Mars"; private int nbSeat = 5; private Color color = Color.black; ... ... // Other fields ... public Car() { } public Car(int nbSeat) { this.nbSeat = nbSeat; } public Car(int nbSeat, Color color) { this.nbSeat = nbSeat; this.color = color; } } 

Les valeurs par défaut spécifiées dans la déclaration des champs ne sont pas toutes fiables. Seuls les champs de name et d’ origin ont des valeurs par défaut.

nbSeat champs nbSeat et color sont d’abord évalués dans leur déclaration, puis ils peuvent être écrasés dans les constructeurs avec des arguments.
Il est sujet aux erreurs et en plus de cette façon de valoriser les champs, la classe diminue son niveau de fiabilité. Comment peut-on compter sur une valeur par défaut atsortingbuée lors de la déclaration des champs alors qu’il s’est avéré non fiable pour deux champs?

2.En utilisant un constructeur pour évaluer tous les champs et en s’appuyant sur les constructeurs, le chaînage est correct

 public class Car { private Ssortingng name; private Ssortingng origin; private int nbSeat; private Color color; ... ... // Other fields ... public Car() { this(5, Color.black); } public Car(int nbSeat) { this(nbSeat, Color.black); } public Car(int nbSeat, Color color) { this.name = "Super car"; this.origin = "Mars"; this.nbSeat = nbSeat; this.color = color; } } 

Cette solution est très bien car elle ne crée pas de duplication, elle regroupe toute la logique à un endroit: le constructeur avec le nombre maximum de parameters.
Il a un seul inconvénient: la nécessité de chaîner l’appel vers un autre constructeur.
Mais est-ce un inconvénient?

3.Donner une valeur par défaut dans le champ intializers pour les champs dont les constructeurs ne leur atsortingbuent pas une nouvelle valeur est mieux mais a toujours des problèmes de duplication

En ne valorisant pas color champs nbSeat et color dans leur déclaration, nous distinguons clairement les champs avec les valeurs par défaut et les champs sans.

 public class Car { private Ssortingng name = "Super car"; private Ssortingng origin = "Mars"; private int nbSeat; private Color color; ... ... // Other fields ... public Car() { nbSeat = 5; color = Color.black; } public Car(int nbSeat) { this.nbSeat = nbSeat; color = Color.black; } public Car(int nbSeat, Color color) { this.nbSeat = nbSeat; this.color = color; } } 

Cette solution est plutôt fine mais elle répète la logique d’instanciation dans chaque constructeur Car contrairement à la solution précédente avec chaînage constructeur.

Dans cet exemple simple, nous pourrions commencer à comprendre le problème de duplication, mais cela semble un peu ennuyeux.
Dans des cas réels, la duplication peut être très importante car le constructeur peut effectuer des calculs et des validations.
Avoir un seul constructeur exécutant la logique d’instanciation devient très utile.

Donc, finalement, l’affectation dans la déclaration des champs n’épargnera pas toujours le constructeur pour le déléguer à un autre constructeur.

Voici une version améliorée.

4.Donner une valeur par défaut dans les champs de saisie pour les champs auxquels les constructeurs ne leur atsortingbuent pas de nouvelle valeur et s’appuyant sur les constructeurs.

 public class Car { private Ssortingng name = "Super car"; private Ssortingng origin = "Mars"; private int nbSeat; private Color color; ... ... // Other fields ... public Car() { this(5, Color.black); } public Car(int nbSeat) { this(nbSeat, Color.black); } public Car(int nbSeat, Color color) { // assignment at a single place this.nbSeat = nbSeat; this.color = color; // validation rules at a single place ... } } 

Cas d’étude 2

Nous allons modifier la classe de Car originale.
Car , Car déclare 5 champs et 3 constructeurs sans relation entre eux.

1.L’utilisation d’un constructeur pour évaluer les champs avec des valeurs par défaut est indésirable

 public class Car { private Ssortingng name; private Ssortingng origin; private int nbSeat; private Color color; private Car replacingCar; ... ... // Other fields ... public Car() { initDefaultValues(); } public Car(int nbSeat, Color color) { initDefaultValues(); this.nbSeat = nbSeat; this.color = color; } public Car(Car replacingCar) { initDefaultValues(); this.replacingCar = replacingCar; // specific validation rules } private void initDefaultValues() { name = "Super car"; origin = "Mars"; } } 

Comme nous ne initDefaultValues() pas les champs name et origin dans leur déclaration et que nous n’avons pas de constructeur commun initDefaultValues() naturellement par d’autres constructeurs, nous sums obligés d’introduire une méthode initDefaultValues() et de l’appeler dans chaque constructeur. Il ne faut donc pas oublier d’appeler cette méthode.
Notez que nous pourrions initDefaultValues() corps initDefaultValues() dans le constructeur no arg mais invoquer this() sans aucun argument de l’autre constructeur n’est pas forcément naturel et peut être facilement oublié:

 public class Car { private Ssortingng name; private Ssortingng origin; private int nbSeat; private Color color; private Car replacingCar; ... ... // Other fields ... public Car() { name = "Super car"; origin = "Mars"; } public Car(int nbSeat, Color color) { this(); this.nbSeat = nbSeat; this.color = color; } public Car(Car replacingCar) { this(); this.replacingCar = replacingCar; // specific validation rules } } 

2.Donner une valeur par défaut dans les initialiseurs de champs pour les champs auxquels les constructeurs ne leur atsortingbuent pas une nouvelle valeur

 public class Car { private Ssortingng name = "Super car"; private Ssortingng origin = "Mars"; private int nbSeat; private Color color; private Car replacingCar; ... ... // Other fields ... public Car() { } public Car(int nbSeat, Color color) { this.nbSeat = nbSeat; this.color = color; } public Car(Car replacingCar) { this.replacingCar = replacingCar; // specific validation rules } } 

Ici, il n’est pas nécessaire d’avoir une méthode initDefaultValues() ou un constructeur no arg pour appeler. Les initialiseurs de champs sont parfaits.


Conclusion

Dans tous les cas, la validation des champs dans les initialiseurs de champs ne doit pas être effectuée pour tous les champs, mais uniquement pour ceux qui ne peuvent pas être écrasés par un constructeur.

Cas d’utilisation 1) Dans le cas de plusieurs constructeurs avec un traitement commun entre eux, il s’agit principalement de l’opinion.
Solution 2 ( Utiliser le constructeur pour valoriser tous les champs et s’appuyer sur le chaînage des constructeurs ) et la solution 4 ( Donner une valeur par défaut dans les intialiseurs de champs pour les champs auxquels les constructeurs ne leur atsortingbuent pas une nouvelle valeur et s’appuyer sur le chaînage des constructeurs ) , des solutions maintenables et robustes.

Cas d’utilisation 2) Dans le cas de constructeurs multiples sans traitement / relation commune entre eux comme dans le cas du constructeur unique, la solution 2 ( Donner une valeur par défaut dans les champs intializers pour les champs auxquels les constructeurs ne leur atsortingbuent pas une nouvelle valeur ) .

La seule différence que je puisse penser est que si vous deviez append un autre constructeur

 public Foo(int inX){ x = inX; } 

alors, dans le premier exemple, vous n’auriez plus de constructeur par défaut alors que dans le deuxième exemple, vous auriez toujours le constructeur par défaut (et pourriez même y faire un appel depuis notre nouveau constructeur si vous le vouliez)