Dovrei usare i blocchi di inizializzatore in Java?


16

Di recente mi sono imbattuto in un costrutto Java che non avevo mai visto prima e mi chiedevo se dovessi usarlo. Sembra essere chiamato blocchi di inizializzazione .

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

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

Il blocco di codice verrà copiato in ciascun costruttore, ovvero se si dispone di più costruttori non è necessario riscrivere il codice.

Tuttavia, vedo tre svantaggi principali usando questa sintassi:

  1. È uno dei pochissimi casi in Java in cui l'ordine del codice è importante, in quanto è possibile definire più blocchi di codice e verranno eseguiti nell'ordine in cui sono scritti. Questo mi sembra dannoso poiché semplicemente cambiando l'ordine dei blocchi di codice si cambierà effettivamente il codice.
  2. Non vedo davvero alcun vantaggio usandolo. Nella maggior parte dei casi, i costruttori si chiameranno a vicenda con alcuni valori predefiniti. Anche se non è così, il codice potrebbe semplicemente essere inserito in un metodo privato e chiamato da ciascun costruttore.
  3. Riduce la leggibilità, poiché potresti mettere il blocco alla fine della classe e il costruttore è normalmente all'inizio della classe. È abbastanza intuitivo esaminare una parte completamente diversa di un file di codice se non si prevede che ciò sia necessario.

Se le mie affermazioni precedenti sono vere, perché (e quando) è stato introdotto questo costrutto linguistico? Ci sono casi d'uso legittimi?


3
L'esempio che hai pubblicato non include nulla che assomigli a un blocco di inizializzatore.
Simon B,

6
@SimonBarker guarda di nuovo - { doStuff(); }a livello di classe è un blocco di inizializzatore.
amon,

@SimonBarker Il blocco di codice che circondadoStuff()
Ripristinare Monica - dirkk


2
"[S] implica che cambiare l'ordine dei blocchi di codice cambierà effettivamente il codice." E in che modo differisce dal cambiare l'ordine degli inizializzatori variabili o delle singole righe di codice? Se non ci sono dipendenze, allora non si verifica alcun danno e se ci sono dipendenze, mettere le dipendenze fuori servizio equivale alle dipendenze errate per le singole righe di codice. Solo perché Java consente di fare riferimento a metodi e classi prima che vengano definiti, ciò non significa che il codice dipendente dall'ordine sia raro in Java.
JAB,

Risposte:


20

Esistono due casi in cui utilizzo i blocchi di inizializzazione.

Il primo è per l'inizializzazione dei membri finali. In Java, è possibile inizializzare un membro finale in linea con la dichiarazione oppure inizializzarlo nel costruttore. In un metodo, è vietato assegnare a un membro finale.

Questo è valido:

final int val = 2;

Anche questo è valido:

final int val;

MyClass() {
    val = 2;
}

Questo non è valido:

final int val;

MyClass() {
    init();
}

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

Se si dispone di più costruttori e se non è possibile inizializzare un membro finale in linea (perché la logica di inizializzazione è troppo complessa) o se i costruttori non possono chiamare se stessi, è possibile copiare / incollare il codice di inizializzazione oppure è possibile utilizzare un blocco di inizializzatore.

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'altro caso d'uso che ho per i blocchi di inizializzazione è per la costruzione di piccole strutture dati di supporto. Dichiaro un membro e inserisco i valori subito dopo le sue dichiarazioni nel suo blocco di inizializzazione.

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

Non è la chiamata del metodo che non è valida. È il codice all'interno del metodo init che non è valido. Solo i costruttori e i blocchi initalizer possono assegnare a una variabile membro finale, quindi l'assegnazione in init non verrà compilata.
Barjak,

Il quarto blocco di codice non viene compilato. I blocchi di Initalizer vengono eseguiti prima di tutti i costruttori, pertanto squareVal = val * valsi lamentano dell'accesso a valori non inizializzati. I blocchi di inizializzatore non possono dipendere da alcun argomento passato al costruttore. La solita soluzione che ho visto a quel tipo di problema è definire un singolo costruttore "base" con la logica complessa e definire tutti gli altri costruttori in termini di quello. La maggior parte degli usi degli inizializzatori di istanza, infatti, può essere sostituita con quel modello.
Malnormalulo,

11

In generale, non usare blocchi di inizializzazione non statici (e forse evitare anche quelli statici).

Sintassi confusa

Guardando questa domanda, ci sono 3 risposte, ma hai ingannato 4 persone con questa sintassi. Ero uno di loro e scrivo Java da 16 anni! Chiaramente, la sintassi è potenzialmente soggetta a errori! Starei lontano da esso.

Costruttori telescopici

Per cose davvero semplici, puoi usare i costruttori "telescopici" per evitare questa confusione:

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

Modello del costruttore

Se hai bisogno di doStuff () alla fine di ogni costruttore o altra sofisticata inizializzazione, forse un modello di generatore sarebbe il migliore. Josh Bloch elenca diversi motivi per cui i costruttori sono una buona idea. I costruttori impiegano un po 'di tempo per scrivere, ma correttamente scritti, sono una gioia da usare.

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

Cicli di inizializzatori statici

Ho usato per usare statici initializers molto, ma a volte sono imbattuto in loop dove 2 classi dipendevano vicenda statica inizializzatore blocchi chiamati prima della classe potrebbe essere completamente caricato. Ciò ha prodotto un "errore nel caricamento della classe" o un messaggio di errore altrettanto vago. Ho dovuto confrontare i file con l'ultima versione funzionante nota nel controllo del codice sorgente per capire quale fosse il problema. Per niente divertente.

Inizializzazione pigra

Forse gli inizializzatori statici sono buoni per motivi di prestazioni quando funzionano e non sono troppo confusi. Ma in generale, al giorno d'oggi preferisco l' inizializzazione lenta agli inizializzatori statici. È chiaro cosa fanno, non ho ancora incontrato un bug di caricamento di classe con loro e funzionano in più situazioni di inizializzazione rispetto ai blocchi di inizializzatore.

Definizione dei dati

Invece dell'inizializzazione statica per la costruzione di strutture di dati, (confronta con gli esempi nelle altre risposte), ora uso le funzioni di supporto alla definizione dei dati immutabili di 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

All'inizio di Java, i blocchi di inizializzazione erano l'unico modo per fare alcune cose, ma ora sono confusi, soggetti a errori e nella maggior parte dei casi sono stati sostituiti da alternative migliori (dettagliate sopra). È interessante conoscere i blocchi di inizializzazione nel caso in cui li vedi nel codice legacy o se si presentano su un test, ma se stavo facendo una revisione del codice e ne avessi visto uno nel nuovo codice, ti chiederei di giustificare perché nessuno dei le alternative sopra erano adatte prima di dare il pollice in alto al codice.


3

Oltre all'inizializzazione di una variabile di istanza dichiarata come final(vedi la risposta di Barjak ), vorrei citare anche il staticblocco di inizializzazione.

Puoi usarli come un tipo di "regolatore statico".

In questo modo è possibile eseguire inizializzazioni complesse su una variabile statica al primo riferimento alla classe.

Ecco un esempio ispirato a quello di 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);
    }
}

1

Per quanto riguarda i blocchi di inizializzazione non statici, la loro funzione nuda è quella di fungere da costruttore predefinito in classi anonime. Questo è fondamentalmente il loro unico diritto di esistere.


0

Sono totalmente d'accordo con le affermazioni 1, 2, 3. Non uso mai inizializzatori a blocchi per questi motivi e non so perché esiste in Java.

Tuttavia, sono costretto a utilizzare l' inizializzatore a blocchi statici in un caso: quando devo istanziare un campo statico il cui costruttore può generare un'eccezione controllata.

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

Ma invece devi fare:

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

Trovo questo idioma molto brutto (ti impedisce anche di contrassegnarlo contextcome final) ma questo è l'unico modo supportato da Java per inizializzare tali campi.


Penso che se si imposta context = null;nel blocco catch, si potrebbe essere in grado di dichiarare il contesto come finale.
GlenPeterson,

@GlenPeterson Ho provato ma non viene compilato:The final field context may already have been assigned
Spotted

oops! Scommetto che puoi rendere definitivo il tuo contesto se introduci una variabile locale all'interno del blocco statico:static { JAXBContext tempCtx = null; try { tempCtx = JAXBContext.newInstance(Foo.class); } catch (JAXBException ignored) { ; } context = tempCtx; }
GlenPeterson,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.