Question

Je me suis donc mis à perfectionner mes compétences en Java et à trouver quelques fonctionnalités que je ne connaissais pas auparavant. Les initialiseurs statiques et d’instance sont deux techniques de ce type.

Ma question est la suivante: quand utiliser un initialiseur au lieu d’inclure le code dans un constructeur? J'ai pensé à deux possibilités évidentes:

  • Les initialiseurs static / instance peuvent être utilisés pour définir la valeur de " final " variables statiques / d'instance alors qu'un constructeur ne peut pas

  • Les initialiseurs statiques peuvent être utilisés pour définir la valeur de toute variable statique dans une classe, ce qui devrait être plus efficace que d'avoir un "if (someStaticVar == null) // do trucs " bloc de code 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", car il ne semble pas y avoir de raison d'utiliser un initialiseur au lieu de simplement définir la valeur lorsque déclarer 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 devrait être utilisé.

On peut certes utiliser un initialiseur pour beaucoup de choses effectuées 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 elle ne vous oblige pas à exécuter ce code lors de l'écriture d'une nouvelle constructeur.

Est-ce que je manque quelque chose? Existe-t-il un certain nombre d'autres situations dans lesquelles un initialiseur devrait être utilisé? Ou est-ce vraiment un outil plutôt limité à utiliser dans des situations très spécifiques?

Était-ce utile?

La solution

Les initialiseurs statiques sont utiles comme cletus mentionné et je les utilise de la même manière. Si vous avez une variable statique à initialiser lors du chargement de la classe, l'initialiseur statique est la solution, d'autant plus qu'il vous permet d'effectuer une initialisation complexe tout en conservant la variable statique final . C'est une grosse victoire.

Je trouve "if (someStaticVar == null) // faire des choses" être salissant et sujet aux erreurs. S'il est initialisé de manière statique et déclaré final , vous évitez la possibilité qu'il soit null .

Cependant, je suis confus quand vous dites:

  

Les initialiseurs static / instance peuvent être utilisés pour définir la valeur de "final".   variables statiques / d'instance alors qu'un constructeur ne peut pas

Je suppose que vous dites les deux:

    Vous pouvez utiliser
  • des initialiseurs statiques pour définir la valeur de "final". variables statiques alors qu'un constructeur ne peut pas
  • Des initialiseurs d'instance
  • peuvent être utilisés pour définir la valeur de "final". variables d'instance alors qu'un constructeur ne peut pas

et vous avez raison sur le premier point, vous avez tort 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, lorsqu'un grand nombre de codes est partagé entre des constructeurs, l'un des meilleurs moyens de gérer cela consiste à les chaîner, en fournissant les valeurs par défaut. Cela rend bien clair ce qui se fait:

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

Autres conseils

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

J’utilise le plus souvent des blocs d’initialisation statiques pour configurer des données statiques finales, en particulier des 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);
  }

  ...
}

Cet exemple peut être réalisé 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 simple, en particulier lorsque les éléments ne sont pas triviaux à initialiser.

Une implémentation naïve peut également ne pas créer de 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 renvoyer à partir de méthodes publiques, etc.

.

Juste pour ajouter 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 auriez besoin d'un bloc synchronisé pour vérifier si les données statiques sont initialisées, puis pour 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, pas au niveau instance. Cela entraîne un coût pour chaque instance construite au lieu d'un coût ponctuel lorsque la classe est chargée. De plus, c'est moche ;-)

J'ai lu tout un article à la recherche d'une réponse à l'ordre d'initialisation des initialiseurs par rapport à leurs constructeurs. Je ne l'ai pas trouvé, alors j'ai é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

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, un initalizer d'instance est préférable pour les classes internes anonymes. Consultez le livre de recettes de JMock pour découvrir un moyen novateur de l'utiliser pour rendre le code plus lisible.

Parfois, si vous avez une logique difficile à enchaîner entre constructeurs (disons que vous êtes en sous-classe et que vous ne pouvez pas appeler this () car vous devez appeler super ()), vous pouvez éviter les doublons en effectuant les tâches courantes. dans l'initalizer d'instance. Les initaliseurs d'instance sont si rares qu'ils constituent une syntaxe surprenante pour beaucoup. Je les évite et préfère rendre ma classe plus concrète et non anonyme si j'ai besoin du comportement du constructeur.

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

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

Les blocs d’initialisation sont des membres de la classe / de l’objet, alors que les constructeurs ne sont pas . Ceci est important lorsque vous envisagez extension / sous-classe :

  1. Les initialiseurs sont hérités par sous-classes. (Bien, peut être ombragé)
    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 cependant pas hérités . (Ils n’appellent que super () [c.-à-d. Pas de paramètres] implicitement ou vous devez effectuer manuellement un appel super (...) spécifique.

    Cela signifie qu'il est possible qu'un appel implicite ou exclusif super (...) ne puisse pas initialiser la sous-classe comme prévu par la classe parente.

Considérons 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:
en cours d'initialisation dans le bloc d'initialisation de: ChildOfParentWithInitializer init
- > Quels que soient les constructeurs implémentés par la sous-classe, 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:
Le constructeur de ChildOfParentWithConstructor a pour valeur null null
- > Cela initialisera le champ sur null par défaut, même s'il ne s'agit peut-être pas du résultat souhaité.

Je voudrais aussi ajouter 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 à gestionnaire de pilotes. C’est l’une des utilisations importantes du bloc de code statique.

Comme vous l'avez dit, dans de nombreux cas, il n'est pas utile et, comme pour toute syntaxe moins utilisée, vous voulez probablement l'éviter simplement pour empêcher la personne suivante qui consulte votre code de passer 30 secondes à l'extraire. les coffres.

Par contre, c’est le seul moyen de faire quelques choses (je pense que vous les avez assez bien couvertes).

De toute façon, les variables statiques elles-mêmes devraient être quelque peu évitées - 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 moi vous en sera reconnaissant.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top