web-dev-qa-db-fra.com

Devrais-je instancier des variables d'instance sur une déclaration ou dans le constructeur?

Y a-t-il un avantage pour l'une ou l'autre approche?

Exemple 1:

class A {
    B b = new B();
}

Exemple 2:

class A {
    B b;

    A() {
         b = new B();
    }
}
204
DonX
  • Il n'y a pas de différence - l'initialisation de la variable d'instance est effectivement placée dans le ou les constructeurs par le compilateur.
  • La première variante est plus lisible.
  • Vous ne pouvez pas gérer les exceptions avec la première variante.
  • Il y a en outre le bloc d'initialisation, qui est également placé dans le ou les constructeurs par le compilateur:

    {
        a = new A();
    }
    

Vérifier Explication et conseil de Sun

De ce tutoriel

Cependant, les déclarations de champ ne font partie d'aucune méthode et ne peuvent donc pas être exécutées comme les instructions. Au lieu de cela, le compilateur Java génère automatiquement le code d'initialisation de champ d'instance et le place dans le ou les constructeurs de la classe. Le code d'initialisation est inséré dans un constructeur dans l'ordre dans lequel il apparaît dans le code source, ce qui signifie qu'un initialiseur de champ peut utiliser les valeurs initiales des champs déclarés avant celui-ci.

De plus, vous pouvez vouloir initialiser paresseusement votre champ. Dans les cas où l’initialisation d’un champ est une opération coûteuse, vous pouvez l’initialiser dès que nécessaire:

ExpensiveObject o;

public ExpensiveObject getExpensiveObject() {
    if (o == null) {
        o = new ExpensiveObject();
    }
    return o;
}

Et finalement (comme l'a souligné Bill), dans l'intérêt de la gestion des dépendances, il est préférable d'utiliser éviter en utilisant l'opérateur new n'importe où dans votre classe. Au lieu de cela, utiliser Injection de dépendance est préférable - c’est-à-dire laisser quelqu'un d'autre (une autre classe/structure) instancier et injecter les dépendances dans votre classe.

250
Bozho

Une autre option serait d'utiliser Injection de dépendance .

class A{
   B b;

   A(B b) {
      this.b = b;
   }
}

Cela supprime la responsabilité de créer l'objet B du constructeur de A. Cela rendra votre code plus testable et plus facile à maintenir à long terme. L'idée est de réduire le couplage entre les deux classes A et B. Un des avantages que cela vous offre est que vous pouvez désormais transmettre tout objet qui étend B (ou implémente B s'il s'agit d'une interface) au constructeur de A et cela fonctionnera. Un inconvénient est que vous renoncez à l'encapsulation de l'objet B pour qu'il soit exposé à l'appelant du constructeur A. Vous devrez déterminer si les avantages en valent la peine, mais ils le sont souvent.

35
Bill the Lizard

Je me suis brûlé d'une manière intéressante aujourd'hui:

class MyClass extends FooClass {
    String a = null;

    public MyClass() {
        super();     // Superclass calls init();
    }

    @Override
    protected void init() {
        super.init();
        if (something)
            a = getStringYadaYada();
    }
}

Voyez l'erreur? Il s'avère que l'initialiseur a = null est appelé après le constructeur de la super-classe est appelé. Comme le constructeur de la superclasse appelle init (), l’initialisation de a est suivie par l’initialisation a = null.

16
Edward Falk

ma "règle" personnelle (presque jamais cassée) est de:

  • déclarer toutes les variables au début de un bloc
  • rendre toutes les variables finales sauf si elles ne peuvent pas être
  • déclarer une variable par ligne
  • ne jamais initialiser une variable où déclaré
  • initialise uniquement quelque chose dans un constructeur .__ quand il a besoin de données provenant de le constructeur pour effectuer l’initialisation

Donc j'aurais un code comme:

public class X
{
    public static final int USED_AS_A_CASE_LABEL = 1; // only exception - the compiler makes me
    private static final int A;
    private final int b;
    private int c;

    static 
    { 
        A = 42; 
    }

    {
        b = 7;
    }

    public X(final int val)
    {
        c = val;
    }

    public void foo(final boolean f)
    {
        final int d;
        final int e;

        d = 7;

        // I will eat my own eyes before using ?: - personal taste.
        if(f)
        {
            e = 1;
        }
        else
        {
            e = 2;
        }
    }
}

De cette façon, je suis toujours à 100% certain de chercher les déclarations de variables (au début d'un bloc) et leurs assignations (dès que cela a du sens après la déclaration). Cela se révèle potentiellement plus efficace également car vous n'initialisez jamais une variable avec une valeur non utilisée (par exemple, declare et init vars puis lève une exception avant que la moitié de ces vars ait besoin d'une valeur). Vous ne finissez pas non plus par faire une initialisation inutile (comme int i = 0; et plus tard, avant que "i" ne soit utilisé, est-ce que i = 5 ;.

J'apprécie beaucoup la cohérence, donc je respecte tout le temps cette règle, et il est beaucoup plus facile de travailler avec le code car vous n'avez pas à chercher pour trouver des éléments. 

Votre kilométrage peut varier.

14
TofuBeer

L'exemple 2 est moins flexible. Si vous ajoutez un autre constructeur, vous devez également vous rappeler d'instancier le champ dans ce constructeur. Il suffit d'instancier directement le champ ou d'introduire le chargement paresseux quelque part dans un getter.

Si l'instanciation nécessite plus qu'une simple new, utilisez un bloc d'initialisation. Cela sera exécuté quel que soit du constructeur utilisé. Par exemple.

public class A {
    private Properties properties;

    {
        try {
            properties = new Properties();
            properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("file.properties"));
        } catch (IOException e) {
            throw new ConfigurationException("Failed to load properties file.", e); // It's a subclass of RuntimeException.
        }
    }

    // ...

}
7
BalusC

Utiliser dependency injection ou lazy initialization est toujours préférable, comme cela a déjà été expliqué en détail dans d'autres réponses.

Lorsque vous ne voulez pas ou ne pouvez pas utiliser ces modèles, et pour les types de données primitifs, il existe trois raisons impérieuses pour lesquelles il est préférable d'initialiser les attributs de classe en dehors du constructeur:

  1. évite les répétitions = si vous avez plus d'un constructeur, ou s'il vous faudra en ajouter d'autres, vous n'aurez plus à répéter l'initialisation dans tous les corps des constructeurs;
  2. lisibilité améliorée = vous pouvez facilement dire en un coup d'œil quelles variables devront être initialisées de l'extérieur de la classe;
  3. lignes de code réduites = pour chaque initialisation effectuée à la déclaration, il y aura une ligne en moins dans le constructeur.
4
Marco Lackovic

Je suppose que c’est presque une question de goût, tant que l’initialisation est simple et n’a pas besoin de logique. 

L’approche constructeur est un peu plus fragile si vous n’utilisez pas de bloc d’initialisation, car si vous ajoutez ultérieurement un deuxième constructeur et oubliez d’initialiser b, vous obtiendrez un b nul lorsque vous utiliserez ce dernier constructeur.

Voir http://Java.Sun.com/docs/books/tutorial/Java/javaOO/initial.html pour plus de détails sur l'initialisation en Java (et pour des explications sur les blocs initalizer et d'autres fonctions d'initialisation peu connues) .

4
Vinko Vrsalovic

Les deux méthodes sont acceptables. Notez que dans ce dernier cas, b=new B() peut ne pas être initialisé si un autre constructeur est présent. Pensez au code d'initialisation situé à l'extérieur du constructeur en tant que constructeur commun et le code est exécuté. 

1
Chandra Patni

Je pense que l'exemple 2 est préférable. Je pense que la meilleure pratique consiste à déclarer en dehors du constructeur et à l'initialiser dans le constructeur.

1
jkeesh

Il y a une autre raison subtile d'initialiser en dehors du constructeur que personne n'a mentionnée auparavant (très spécifique, je dois dire). Si vous utilisez des outils UML pour générer des diagrammes de classes à partir du code (reverse engineering), la plupart des outils noteront l'initialisation de l'exemple 1 et le transféreront dans un diagramme (si vous préférez qu'il affiche les valeurs initiales, comme Je fais). Ils ne prendront pas ces valeurs initiales de l'exemple 2. Là encore, c'est une raison très spécifique: si vous travaillez avec des outils UML, mais une fois que j'ai appris cela, j'essaie de prendre toutes mes valeurs par défaut en dehors du constructeur, sauf mentionné précédemment, il y a un problème de possibilité d'exception ou de logique compliquée.

0
tulu

La deuxième option est préférable car elle permet d’utiliser différentes logiques dans les facteurs pour l’instanciation de classe et d’utiliser les chaînes de chaînage. Par exemple.

class A {
    int b;

    // secondary ctor
    A(String b) {
         this(Integer.valueOf(b));
    }

    // primary ctor
    A(int b) {
         this.b = b;
    }
}

La deuxième option est donc plus flexible.

0
Andriy Kryvtsun

La déclaration se passe avant la construction! Donc si on a initialisé la variable (b dans ce cas) aux deux endroits, l’initialisation du constructeur sera l’initialisation finale. 

0
Imad S

Je n'ai pas vu ce qui suit dans les réponses:

Un avantage possible d'avoir l'initialisation au moment de la déclaration pourrait être avec les IDE d'aujourd'hui où vous pouvez très facilement passer à la déclaration d'une variable (principalement Ctrl-<hover_over_the_variable>-<left_mouse_click>) de n'importe où dans votre code. Vous voyez alors immédiatement la valeur de cette variable. Sinon, vous devez "rechercher" le lieu où l'initialisation est effectuée (principalement: constructeur).

Cet avantage est bien sûr secondaire à tous les autres raisonnements logiques, mais pour certaines personnes, cette "fonctionnalité" pourrait être plus importante. 

0
GeertVc

Le second est un exemple d'initialisation différée. La première est une initialisation plus simple, elles sont essentiellement les mêmes.

0
fastcodejava