web-dev-qa-db-fra.com

Utilisation des initialiseurs et des constructeurs dans Java

J'ai donc revu mes compétences Java récemment) et j'ai trouvé quelques fonctionnalités que je ne connaissais pas auparavant. Les initialiseurs statiques et d'initialisation sont deux de ces techniques.

Ma question est quand utiliserait-on un initialiseur au lieu d'inclure le code dans un constructeur? J'ai pensé à quelques possibilités évidentes:

  • 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

  • les initialiseurs statiques peuvent être utilisés pour définir la valeur de toutes les variables statiques dans une classe, ce qui devrait être plus efficace que d'avoir un bloc de code "if (someStaticVar == null) // do stuff" au début de chaque constructeur

Ces deux cas supposent que le code requis 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, bien que ces gains ne soient pas triviaux (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 se 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 private initialize () me semble plus logique que d'utiliser un initialiseur car cela ne vous empêche pas d'exécuter ce code lors de l'écriture d'un nouveau constructeur.

Suis-je en train de manquer quelque chose? Existe-t-il un certain nombre d'autres situations dans lesquelles un initialiseur doit être utilisé? Ou est-ce vraiment juste un outil assez limité à utiliser dans des situations très spécifiques?

92
Inertiatic

Les initialiseurs statiques sont utiles comme cletus l'a mentionné et je les utilise de la même manière. Si vous avez une variable statique qui doit être initialisée lorsque la classe est chargée, alors un initialiseur statique est le chemin à parcourir, d'autant plus qu'il vous permet de faire une initialisation complexe et que la variable statique est toujours final . C'est une grosse victoire.

Je trouve que "if (someStaticVar == null) // do stuff" est désordonné et sujet aux erreurs. S'il est initialisé statiquement et déclaré final, alors vous évitez qu'il ne 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 plus, 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 rend assez clair ce qui est fait:

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

Les classes internes anonymes ne peuvent pas avoir de constructeur (car elles sont anonymes), elles sont donc assez adaptées aux initialiseurs d'instance.

52
Alex Martelli

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<String> SUITS;

  static {
    List<String> list = new ArrayList<String>();
    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<String> 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 triviaux à initialiser.

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

26
cletus

Juste pour ajouter quelques points déjà excellents ici. L'initialiseur statique est sûr pour les threads. Il est exécuté lorsque la classe est chargée, et rend donc l'initialisation des données statiques plus simple que l'utilisation d'un constructeur, dans lequel vous auriez besoin d'un bloc synchronisé pour vérifier si les données statiques sont initialisées, puis réellement les 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"));
        }
    }

versus

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 ;-)

14
Robin

J'ai lu un article entier à la recherche d'une réponse à l'ordre d'initialisation des initialiseurs par rapport à leurs constructeurs. Je ne l'ai pas trouvé, j'ai donc écrit du code pour vérifier ma compréhension. J'ai pensé ajouter cette petite démonstration en 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(String[] 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
13
Daniel

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

En général, un initaliseur d'instance est préférable pour les classes internes anonymes. Jetez un oeil à livre de cuisine de JMock pour voir une façon innovante de l'utiliser pour rendre le code plus lisible.

Parfois, si vous avez une logique compliquée à enchaîner entre les constructeurs (par exemple, vous sous-classez et vous ne pouvez pas appeler this () parce que vous devez appeler super ()), vous pouvez éviter la duplication en faisant les choses courantes dans l'instance initaliseur. Les initaliseurs d'instance sont si rares, cependant, qu'ils sont une syntaxe surprenante pour beaucoup, donc je les évite et préfère rendre ma classe 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é.

7
Yishai

Il y a un aspect important que vous devez considérer dans votre choix:

les blocs d'initialisation sont membres de la classe/objet, tandis que les constructeurs ne le sont pas. Ceci est important lorsque l'on considère extension/sous-classification:

  1. Les initialiseurs sont hérités par sous-classes. (Cependant, peut être masqué)
    Cela signifie qu'il est fondamentalement garanti que les sous-classes sont initialisées comme prévu par la classe parente.
  2. Les constructeurs ne sont pas hérités, cependant. (Ils appellent uniquement super() [c'est-à-dire aucun paramètre] implicitement ou vous devez effectuer manuellement un appel spécifique à super(...).)
    Cela signifie qu'il est possible qu'un appel implicite ou exclu super(...) n'initialise pas la sous-classe comme prévu par la classe parente.

Considérez cet exemple de bloc d'initialisation:

class ParentWithInitializer {
    protected final String aFieldToInitialize;

    {
        aFieldToInitialize = "init";
        System.out.println("initializing in initializer block of: " 
            + this.getClass().getSimpleName());
    }
}

class ChildOfParentWithInitializer extends ParentWithInitializer{
    public static void main(String... args){
        System.out.println(new ChildOfParentWithInitializer().aFieldToInitialize);
    }
}

sortie:
initializing in initializer block of: ChildOfParentWithInitializer init
-> Peu importe les constructeurs que la sous-classe implémente, le champ sera initialisé.

Considérons maintenant cet exemple avec les constructeurs:

class ParentWithConstructor {
    protected final String 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(String... 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 (String... 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 peut-être pas le résultat souhaité.

6
Vankog

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

4
kmrinal

Comme vous l'avez mentionné, ce n'est pas utile dans de nombreux cas et comme pour toute syntaxe moins utilisée, vous voudrez probablement l'éviter juste pour empêcher la prochaine personne qui regarde votre code de passer les 30 secondes pour le retirer des coffres.

D'un autre côté, c'est la seule façon de faire quelques choses (je pense que vous les avez assez bien 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 pourriez trouver des approches différentes, votre futur vous en sera reconnaissant.

3
Bill K