Dois-je utiliser des blocs d'initialisation en Java?

16

J'ai récemment rencontré une construction Java que je n'avais jamais vue auparavant et je me demandais si je devais l'utiliser. Il semble être appelé blocs d'initialisation .

public class Test {
  public Test() { /* first constructor */ }
  public Test(String s) { /* second constructor */ }

  // Non-static initializer block - copied into every constructor:
  {
    doStuff();
  }
}

Le bloc de code sera copié dans chaque constructeur, c'est-à-dire que si vous avez plusieurs constructeurs, vous n'avez pas à réécrire le code.

Cependant, je vois trois principaux inconvénients en utilisant cette syntaxe:

  1. C'est l'un des très rares cas en Java où l'ordre de votre code est important, car vous pouvez définir plusieurs blocs de code et ils seront exécutés dans l'ordre où ils sont écrits. Cela me semble nuisible car le simple fait de changer l'ordre des blocs de code va en fait changer le code.
  2. Je ne vois vraiment aucun avantage à l'utiliser. Dans la plupart des cas, les constructeurs s'appelleront avec des valeurs prédéfinies. Même si ce n'est pas le cas, le code pourrait simplement être placé dans une méthode privée et appelé depuis chaque constructeur.
  3. Cela réduit la lisibilité, car vous pourriez mettre le bloc à la fin de la classe et le constructeur est normalement au début de la classe. Il est assez contre-intuitif de regarder une partie complètement différente d'un fichier de code si vous ne vous attendez pas à ce que ce soit nécessaire.

Si mes affirmations ci-dessus sont vraies, pourquoi (et quand) cette construction de langage a-t-elle été introduite? Existe-t-il des cas d'utilisation légitimes?

Réintégrer Monica - dirkk
la source
3
L'exemple que vous avez publié n'inclut rien qui ressemble à un bloc d'initialisation.
Simon B
6
@SimonBarker regarde à nouveau - au niveau { doStuff(); }de la classe est un bloc d'initialisation.
amon
@SimonBarker Le bloc de code qui entouredoStuff()
Réintégrer Monica - dirkk
2
"[S] implique que changer l'ordre des blocs de code va réellement changer le code." Et en quoi est-ce différent du changement de l'ordre des initialiseurs de variables ou des lignes de code individuelles? S'il n'y a pas de dépendances, aucun dommage ne se produit, et s'il y a des dépendances, le fait de mettre les dépendances dans le désordre équivaut à une mauvaise gestion des dépendances pour des lignes de code individuelles. Ce n'est pas parce que Java vous permet de faire référence aux méthodes et aux classes avant qu'elles ne soient définies que le code dépendant de l'ordre est rare en Java.
JAB

Réponses:

20

Il y a deux cas où j'utilise des blocs d'initialisation.

Le premier sert à initialiser les membres finaux. En Java, vous pouvez initialiser un membre final soit en ligne avec la déclaration, soit vous pouvez l'initialiser dans le constructeur. Dans une méthode, il est interdit d'assigner à un membre final.

Ceci est valable:

final int val = 2;

Ceci est également valable:

final int val;

MyClass() {
    val = 2;
}

Ceci n'est pas valide:

final int val;

MyClass() {
    init();
}

void init() {
    val = 2;  // cannot assign to 'final' field in a method
}

Si vous avez plusieurs constructeurs et si vous ne pouvez pas initialiser un membre final en ligne (car la logique d'initialisation est trop complexe), ou si les constructeurs ne peuvent pas s'appeler eux-mêmes, vous pouvez soit copier / coller le code d'initialisation, soit utiliser un bloc d'initialisation.

final int val;
final int squareVal;

MyClass(int v, String s) {
    this.val = v;
    this.s = s;
}

MyClass(Point p, long id) {
    this.val = p.x;
    this.id = id;
}

{
    squareVal = val * val;
}

L'autre cas d'utilisation que j'ai pour les blocs d'initialisation est pour la construction de petites structures de données d'assistance. Je déclare un membre et y mets des valeurs juste après ses déclarations dans son propre bloc d'initialisation.

private Map<String, String> days = new HashMap<String, String>();
{
    days.put("mon", "monday");
    days.put("tue", "tuesday");
    days.put("wed", "wednesday");
    days.put("thu", "thursday");
    days.put("fri", "friday");
    days.put("sat", "saturday");
    days.put("sun", "sunday");
}
barjak
la source
Ce n'est pas l'appel de méthode qui n'est pas valide. C'est le code à l'intérieur de la méthode init qui n'est pas valide. Seuls les constructeurs et les blocs initalizer peuvent être affectés à une variable de membre final, donc l'affectation dans init ne sera pas compilée.
barjak
Votre quatrième bloc de code ne se compile pas. Les blocs Initalizer s'exécutent avant tous les constructeurs et squareVal = val * valse plaindront donc d'accéder aux valeurs non initialisées. Les blocs d'initialisation ne peuvent pas dépendre des arguments passés au constructeur. La solution habituelle que j'ai vue à ce genre de problème est de définir un seul constructeur "de base" avec la logique complexe, et de définir tous les autres constructeurs en termes de celui-ci. En fait, la plupart des utilisations des initialiseurs d'instance peuvent être remplacées par ce modèle.
Malnormalulo
11

En général, n'utilisez pas de blocs d'initialisation non statiques (et évitez peut-être aussi les blocs statiques).

Syntaxe déroutante

En regardant cette question, il y a 3 réponses, mais vous avez trompé 4 personnes avec cette syntaxe. J'étais l'un d'eux et j'écris Java depuis 16 ans! De toute évidence, la syntaxe est potentiellement sujette aux erreurs! Je m'en éloignerais.

Constructeurs télescopiques

Pour des choses vraiment simples, vous pouvez utiliser des constructeurs "télescopiques" pour éviter cette confusion:

public class Test {
    private String something;

    // Default constructor does some things
    public Test() { doStuff(); }

    // Other constructors call the default constructor
    public Test(String s) {
        this(); // Call default constructor
        something = s;
    }
}

Modèle de générateur

Si vous avez besoin de doStuff () à la fin de chaque constructeur ou d'une autre initialisation sophistiquée, un modèle de générateur serait peut-être préférable. Josh Bloch énumère plusieurs raisons pour lesquelles les constructeurs sont une bonne idée. Les constructeurs prennent un peu de temps à écrire, mais correctement écrits, ils sont une joie à utiliser.

public class Test {
    // Value can be final (immutable)
    private final String something;

    // Private constructor.
    private Test(String s) { something = s; }

    // Static method to get a builder
    public static Builder builder() { return new Builder(); }

    // builder class accumulates values until a valid Test object can be created. 
    private static class Builder {
        private String tempSomething;
        public Builder something(String s) {
            tempSomething = s;
            return this;
        }
        // This is our factory method for a Test class.
        public Test build() {
            Test t = new Test(tempSomething);
            // Here we do your extra initialization after the
            // Test class has been created.
            doStuff();
            // Return a valid, potentially immutable Test object.
            return t;
        }
    }
}

// Now you can call:
Test t = Test.builder()
             .setString("Utini!")
             .build();

Boucles d'initialisation statique

J'utilisais beaucoup les initialiseurs statiques , mais je rencontrais parfois des boucles où 2 classes dépendaient des blocs d'initialisation statiques de l'autre avant que la classe puisse être entièrement chargée. Cela a produit un "échec de chargement de classe" ou un message d'erreur tout aussi vague. J'ai dû comparer des fichiers avec la dernière version de travail connue dans le contrôle de code source afin de comprendre quel était le problème. Pas du tout amusant.

Initialisation paresseuse

Peut-être que les initialiseurs statiques sont bons pour des raisons de performances lorsqu'ils fonctionnent et ne sont pas trop déroutants. Mais en général, je préfère l' initialisation paresseuse aux initialiseurs statiques de nos jours. Il est clair ce qu'ils font, je n'ai pas encore rencontré de bogue de chargement de classe avec eux, et ils fonctionnent dans plus de situations d'initialisation que les blocs d'initialisation.

Définition des données

Au lieu de l'initialisation statique pour la construction de structures de données, (comparer avec des exemples dans les autres réponses), j'utilise maintenant les fonctions d'assistance de définition de données immuables de Paguro :

private ImMap<String,String> days =
        map(tup("mon", "monday"),
            tup("tue", "tuesday"),
            tup("wed", "wednesday"),
            tup("thu", "thursday"),
            tup("fri", "friday"),
            tup("sat", "saturday"),
            tup("sun", "sunday"));

Conculsion

Au début de Java, les blocs d'initialisation étaient le seul moyen de faire certaines choses, mais maintenant ils sont déroutants, sujets aux erreurs et, dans la plupart des cas, ont été remplacés par de meilleures alternatives (détaillées ci-dessus). Il est intéressant de connaître les blocs d'initialisation au cas où vous les verriez dans le code hérité, ou s'ils reviennent sur un test, mais si je faisais une revue de code et que j'en voyais un dans du nouveau code, je vous demanderais de justifier pourquoi aucun des Les alternatives ci-dessus étaient appropriées avant de donner votre accord.

GlenPeterson
la source
3

En plus de l'initialisation d'une variable d'instance qui est déclarée comme final(voir la réponse de barjak ), je mentionnerais également le staticbloc d'initialisation.

Vous pouvez les utiliser comme une sorte de "constructeur statique".

De cette façon, vous pouvez effectuer des initialisations complexes sur une variable statique la première fois que la classe est référencée.

Voici un exemple inspiré de celui de barjak:

public class dayHelper(){
    private static Map<String, String> days = new HashMap<String, String>();
    static {
        days.put("mon", "monday");
        days.put("tue", "tuesday");
        days.put("wed", "wednesday");
        days.put("thu", "thursday");
        days.put("fri", "friday");
        days.put("sat", "saturday");
        days.put("sun", "sunday");
    }
    public static String getLongName(String shortName){
         return days.get(shortName);
    }
}
C.Champagne
la source
1

En ce qui concerne les blocs d'initialisation non statiques, leur fonction nue consiste à agir comme constructeur par défaut dans les classes anonymes. C'est fondamentalement leur seul droit d'exister.

Nico
la source
0

Je suis totalement d'accord avec les déclarations 1, 2, 3. Je n'utilise jamais non plus d'initialiseurs de bloc pour ces raisons et je ne sais pas pourquoi il existe en Java.

Cependant, je suis obligé d'utiliser l' initialiseur de bloc statique dans un cas: quand je dois instancier un champ statique dont le constructeur peut lever une exception vérifiée.

private static final JAXBContext context = JAXBContext.newInstance(Foo.class); //doesn't compile

Mais à la place, vous devez faire:

private static JAXBContext context;
static {
    try
    {
        context = JAXBContext.newInstance(Foo.class);
    }
    catch (JAXBException e)
    {
        //seriously...
    }
}

Je trouve cet idiome très moche (il vous empêche également de marquer contextcomme final) mais c'est le seul moyen pris en charge par Java pour initialiser de tels champs.

Pointé
la source
Je pense que si vous définissez context = null;votre bloc catch, vous pourrez peut-être déclarer le contexte comme final.
GlenPeterson
@GlenPeterson J'ai essayé mais il ne compile pas:The final field context may already have been assigned
Spotted
Oups! Je parie que vous pouvez rendre votre contexte final si vous introduisez une variable locale à l'intérieur du bloc statique:static { JAXBContext tempCtx = null; try { tempCtx = JAXBContext.newInstance(Foo.class); } catch (JAXBException ignored) { ; } context = tempCtx; }
GlenPeterson