Devo utilizzare i blocchi di inizializzazione in Java?

14

Recentemente 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 sarà copiato in ogni costruttore, cioè se hai un costruttore multiplo non devi riscrivere il codice.

Tuttavia, vedo tre principali svantaggi 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 in quanto semplicemente cambiando l'ordine dei blocchi di codice in realtà cambierà 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 questo non è il caso, il codice potrebbe semplicemente essere messo in un metodo privato e chiamato da ogni costruttore.
  3. Riduce la leggibilità, in quanto è possibile inserire il blocco alla fine della classe e normalmente il costruttore si trova all'inizio della classe. È abbastanza controintuitivo guardare una parte completamente diversa di un file di codice se non ci si aspetta che sia necessario.

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

    
posta dirkk 12.05.2014 - 16:50
fonte

5 risposte

20

Ci sono due casi in cui utilizzo i blocchi di inizializzazione.

Il primo è per inizializzare i membri finali. In Java, è possibile inizializzare un membro finale in linea con la dichiarazione, oppure è possibile 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 hanno 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, allora è possibile copiare / incollare il codice di inizializzazione, oppure puoi utilizzare un blocco di inizializzazione.

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 di dati di supporto. Dichiaro un membro e inserisco i valori subito dopo le dichiarazioni nel proprio 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");
}
    
risposta data 12.05.2014 - 17:27
fonte
10

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

Sintesi confusa

Guardando questa domanda, ci sono 3 risposte, ma hai ingannato 4 persone con questa sintassi. Ero uno di loro e ho scritto Java per 16 anni! Chiaramente, la sintassi è potenzialmente soggetta a errori! Me ne starei lontano.

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 generatore

Se hai bisogno di fareStuff () alla fine di ogni costruttore o altre inizializzazioni sofisticate, forse un modello di builder sarebbe il migliore. Josh Bloch elenca diversi motivi per cui i costruttori sono una buona idea. I costruttori richiedono un po 'di tempo per scrivere, ma se scritti correttamente, 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();

Loop inizializzatori statici

Ho usato molto spesso gli inizializzatori static , ma occasionalmente ho eseguito loop in cui 2 classi dipendevano dai blocchi di inizializzazione statici che venivano chiamati prima che la classe potesse essere caricata completamente. Ciò ha prodotto un messaggio di errore "impossibile caricare la classe" o un messaggio di errore simile. Ho dovuto confrontare i file con l'ultima versione funzionante conosciuta nel controllo del codice sorgente per capire quale fosse il problema. Nessun divertimento.

Inizializzazione pigra

Forse gli inizializzatori statici sono buoni per motivi di prestazioni quando funzionano e non sono troppo confusi. Ma in generale, sto preferendo inizializzazione pigra agli inizializzatori statici in questi giorni. È chiaro che cosa fanno, non ho ancora incontrato bug di caricamento di classe e lavorano in più situazioni di inizializzazione rispetto ai blocchi di inizializzazione.

Definizione dei dati

Invece dell'inizializzazione statica per la costruzione di strutture dati, (confronta con esempi nelle altre risposte), ora utilizzo Funzioni helper di 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 in codice legacy, oppure vengono visualizzati su un test, ma se dovessi fare la revisione del codice e ne ho visto uno in un nuovo codice, ti chiederei di giustificare il motivo per cui nessuno dei le alternative sopra erano adatte prima di dare il pollice al codice.

    
risposta data 24.06.2016 - 21:15
fonte
3

Oltre all'inizializzazione di una variabile di istanza dichiarata come final (vedi risposta di barjak ) , Vorrei anche menzionare il blocco di inizializzazione static .

Puoi usarli come una specie di "contructor 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);
    }
}
    
risposta data 12.05.2014 - 17:44
fonte
1

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

    
risposta data 24.06.2016 - 20:28
fonte
0

Sono totalmente d'accordo con le dichiarazioni 1, 2, 3. Non uso mai gli inizializzatori di blocco per questi motivi e non so perché esiste in Java.

Tuttavia, sono costretto a utilizzare l'inizializzatore del blocco static 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 che questo idioma sia molto brutto (impedisce anche di contrassegnare context come final ) ma questo è l'unico modo supportato da Java per inizializzare tali campi.

    
risposta data 25.06.2016 - 09:08
fonte

Leggi altre domande sui tag