Utilisation des initialiseurs vs constructeurs en Java

J’ai donc récemment amélioré mes compétences en Java et j’ai trouvé quelques fonctionnalités que je ne connaissais pas auparavant. Les initialiseurs statiques et d’instance sont deux de ces techniques.

Ma question est de savoir quand utiliser un initialiseur au lieu d’inclure le code dans un constructeur? J’ai pensé à quelques possibilités évidentes:

Ces deux cas supposent que le code requirejs pour définir ces variables est plus complexe que simplement “var = valeur”, sinon il ne semble pas y avoir de raison d’utiliser un initialiseur au lieu de simplement définir la valeur lors de la déclaration de la variable.

Cependant, même s’il ne s’agit pas de gains sortingviaux (en particulier la possibilité de définir une variable finale), il semble qu’il existe un nombre assez limité de situations dans lesquelles un initialiseur doit être utilisé.

On peut certainement utiliser un initialiseur pour beaucoup de ce qui est fait dans un constructeur, mais je ne vois pas vraiment la raison de le faire. Même si tous les constructeurs d’une classe partagent une grande quantité de code, l’utilisation d’une fonction initiale initialize () semble avoir plus de sens pour moi que l’utilisation d’un initialiseur, car cela ne vous empêche pas d’exécuter ce code lors de l’écriture d’un nouveau code. constructeur.

Est-ce que je manque quelque chose? Existe-t-il un certain nombre d’autres situations dans lesquelles un initialiseur doit être utilisé? Ou est-ce vraiment un outil assez limité pour être utilisé dans des situations très spécifiques?

Les initialiseurs statiques sont utiles comme mentionné ci-dessus et je les utilise de la même manière. Si vous avez une variable statique à initialiser lors du chargement de la classe, un initialiseur statique est la solution, d’autant plus qu’il vous permet d’effectuer une initialisation complexe et que la variable statique est final . C’est une grande victoire.

Je trouve “if (someStaticVar == null) // faire des trucs” être compliqué et sujet aux erreurs. Si elle est initialisée statiquement et déclarée final , alors vous évitez la possibilité qu’elle soit null .

Cependant, je suis confus quand vous dites:

les initialiseurs statiques / d’instance peuvent être utilisés pour définir la valeur des variables statiques / d’instance “finales”, alors qu’un constructeur ne peut pas

Je suppose que vous dites les deux:

  • Les initialiseurs statiques peuvent être utilisés pour définir la valeur des variables statiques “finales” alors qu’un constructeur ne peut pas
  • les initialiseurs d’instance peuvent être utilisés pour définir la valeur des variables d’instance “finales” alors qu’un constructeur ne peut pas

et vous avez raison sur le premier point, faux sur le second. Vous pouvez, par exemple, faire ceci:

 class MyClass { private final int counter; public MyClass(final int counter) { this.counter = counter; } } 

De même, lorsque beaucoup de code est partagé entre les constructeurs, l’une des meilleures façons de gérer cela est de chaîner les constructeurs, en fournissant les valeurs par défaut. Cela montre clairement ce qui se fait:

 class MyClass { private final int counter; public MyClass() { this(0); } public MyClass(final int counter) { this.counter = counter; } } 

Les classes internes anonymes ne peuvent pas avoir de constructeur (car elles sont anonymes), elles conviennent donc parfaitement aux initialiseurs d’instances.

J’utilise le plus souvent des blocs d’initialisation statiques pour configurer les données statiques finales, en particulier les collections. Par exemple:

 public class Deck { private final static List SUITS; static { List list = new ArrayList(); list.add("Clubs"); list.add("Spades"); list.add("Hearts"); list.add("Diamonds"); SUITS = Collections.unmodifiableList(list); } ... } 

Maintenant, cet exemple peut être fait avec une seule ligne de code:

 private final static List SUITS = Collections.unmodifiableList( Arrays.asList("Clubs", "Spades", "Hearts", "Diamonds") ); 

mais la version statique peut être beaucoup plus nette, en particulier lorsque les éléments ne sont pas sortingviaux à initialiser.

Une implémentation naïve peut également ne pas créer une liste non modifiable, ce qui constitue une erreur potentielle. Ce qui précède crée une structure de données immuable que vous pouvez facilement renvoyer des méthodes publiques, etc.

Juste pour append quelques points déjà excellents ici. L’initialiseur statique est thread-safe. Il est exécuté lorsque la classe est chargée, ce qui simplifie l’initialisation des données statiques par rapport à l’utilisation d’un constructeur, dans lequel vous avez besoin d’un bloc synchronisé pour vérifier si les données statiques sont initialisées puis pour l’initialiser.

 public class MyClass { static private Properties propTable; static { try { propTable.load(new FileInputStream("/data/user.prop")); } catch (Exception e) { propTable.put("user", System.getProperty("user")); propTable.put("password", System.getProperty("password")); } } 

contre

 public class MyClass { public MyClass() { synchronized (MyClass.class) { if (propTable == null) { try { propTable.load(new FileInputStream("/data/user.prop")); } catch (Exception e) { propTable.put("user", System.getProperty("user")); propTable.put("password", System.getProperty("password")); } } } } 

N’oubliez pas, vous devez maintenant synchroniser au niveau de la classe et non au niveau de l’instance. Cela entraîne un coût pour chaque instance construite au lieu d’un coût unique lorsque la classe est chargée. De plus, c’est moche 😉

J’ai lu un article entier à la recherche d’une réponse à l’ordre d’initialisation par rapport à leurs constructeurs. Je ne l’ai pas trouvé, j’ai donc écrit un code pour vérifier ma compréhension. Je pensais append cette petite démonstration en guise de commentaire. Pour tester votre compréhension, voyez si vous pouvez prédire la réponse avant de la lire en bas.

 /** * Demonstrate order of initialization in Java. * @author Daniel S. Wilkerson */ public class CtorOrder { public static void main(Ssortingng[] args) { B a = new B(); } } class A { A() { System.out.println("A ctor"); } } class B extends A { int x = initX(); int initX() { System.out.println("B initX"); return 1; } B() { super(); System.out.println("B ctor"); } } 

Sortie:

 java CtorOrder A ctor B initX B ctor 

Un initialiseur statique est l’équivalent d’un constructeur dans le contexte statique. Vous verrez certainement cela plus souvent qu’un initialiseur d’instance. Parfois, vous devez exécuter du code pour configurer l’environnement statique.

En général, l’initalisation d’instance est la meilleure pour les classes internes anonymes. Jetez un coup d’œil au livre de recettes de JMock pour découvrir une manière innovante de l’utiliser pour rendre le code plus lisible.

Parfois, si vous avez une logique compliquée à enchaîner sur des constructeurs (par exemple, si vous sous-classez et que vous ne pouvez pas appeler this () parce que vous devez appeler super ()), vous pouvez éviter les doublons en faisant les choses courantes dans l’instance Initalizer. Les initalisateurs d’instance sont si rares, cependant, qu’ils sont une syntaxe surprenante pour beaucoup, donc je les évite et ferais plutôt que ma classe soit concrète et non anonyme si j’ai besoin du comportement du constructeur.

JMock est une exception, car c’est ainsi que le framework est destiné à être utilisé.

Je voudrais également append un point avec toutes les réponses fabuleuses ci-dessus. Lorsque nous chargeons un pilote dans JDBC à l’aide de Class.forName (“”), le chargement de la classe se produit et l’initialiseur statique de la classe Driver est déclenché et le code qu’il contient enregistre Driver to Driver Manager. C’est l’une des utilisations importantes du bloc de code statique.

Comme vous l’avez mentionné, ce n’est pas utile dans beaucoup de cas et comme avec toute syntaxe moins utilisée, vous voudrez probablement l’éviter simplement pour empêcher la prochaine personne qui consulte votre code de consacrer 30 secondes à le sortir des coffres.

D’un autre côté, c’est la seule façon de faire certaines choses (je pense que vous les avez pratiquement toutes couvertes).

Les variables statiques elles-mêmes devraient être quelque peu évitées de toute façon – pas toujours, mais si vous en utilisez beaucoup ou si vous en utilisez beaucoup dans une classe, vous trouverez peut-être des approches différentes, votre avenir personnel vous en sera reconnaissant.

Il y a un aspect important que vous devez prendre en compte dans votre choix:

Les blocs d’initialisation sont membres de la classe / object, alors que les constructeurs ne le sont pas . Ceci est important lorsque l’on considère l’ extension / sous-classement :

  1. Les initialiseurs sont hérités par les sous-classes. (Bien, peut être ombré)
    Cela signifie qu’il est essentiellement garanti que les sous-classes sont initialisées comme prévu par la classe parente.
  2. Les constructeurs ne sont cependant pas hérités . (Ils appellent seulement super() [c’est-à-dire pas de parameters] implicitement ou vous devez faire un appel spécifique super(...) manuellement.)
    Cela signifie qu’il est possible qu’un appel implicite ou exclusif de super(...) ne initialise pas la sous-classe comme prévu par la classe parente.

Prenons l’exemple de ce bloc d’initialisation:

 class ParentWithInitializer { protected final Ssortingng aFieldToInitialize; { aFieldToInitialize = "init"; System.out.println("initializing in initializer block of: " + this.getClass().getSimpleName()); } } class ChildOfParentWithInitializer extends ParentWithInitializer{ public static void main(Ssortingng... args){ System.out.println(new ChildOfParentWithInitializer().aFieldToInitialize); } } 

sortie:
initializing in initializer block of: ChildOfParentWithInitializer init
-> Quels que soient les constructeurs implémentés par la sous-classe, le champ sera initialisé.

Considérons maintenant cet exemple avec des constructeurs:

 class ParentWithConstructor { protected final Ssortingng aFieldToInitialize; // different constructors initialize the value differently: ParentWithConstructor(){ //init a null object aFieldToInitialize = null; System.out.println("Constructor of " + this.getClass().getSimpleName() + " inits to null"); } ParentWithConstructor(Ssortingng... params) { //init all fields to intended values aFieldToInitialize = "intended init Value"; System.out.println("initializing in parameterized constructor of:" + this.getClass().getSimpleName()); } } class ChildOfParentWithConstructor extends ParentWithConstructor{ public static void main (Ssortingng... args){ System.out.println(new ChildOfParentWithConstructor().aFieldToInitialize); } } 

sortie:
Constructor of ChildOfParentWithConstructor inits to null null
-> Cela initialisera le champ à null par défaut, même si ce n’est pas le résultat souhaité.