Domanda

Quindi, ultimamente, ho ripulito le mie abilità Java e ho trovato alcune funzionalità che non conoscevo in precedenza. Gli inizializzatori statici e di istanza sono due di queste tecniche.

La mia domanda è quando si dovrebbe usare un inizializzatore invece di includere il codice in un costruttore? Ho pensato a un paio di ovvie possibilità:

  • inizializzatori statici / di istanza possono essere usati per impostare il valore di "finale" variabili statiche / di istanza mentre un costruttore non può

  • gli inizializzatori statici possono essere usati per impostare il valore di qualsiasi variabile statica in una classe, che dovrebbe essere più efficiente di avere un " if (someStaticVar == null) // do stuff " blocco di codice all'inizio di ciascun costruttore

Entrambi questi casi presuppongono che il codice richiesto per impostare queste variabili sia più complesso della semplice "quot = var = value", poiché altrimenti non sembrerebbe esserci alcun motivo per usare un inizializzatore invece di impostare semplicemente il valore quando dichiarando la variabile.

Tuttavia, sebbene questi non siano guadagni banali (specialmente la possibilità di impostare una variabile finale), sembra che ci sia un numero piuttosto limitato di situazioni in cui un inizializzatore dovrebbe essere usato.

Si può certamente usare un inizializzatore per molto di ciò che viene fatto in un costruttore, ma non vedo davvero il motivo per farlo. Anche se tutti i costruttori per una classe condividono una grande quantità di codice, l'uso di una funzione di inizializzazione privata () sembra avere più senso per me rispetto all'utilizzo di un inizializzatore perché non ti impedisce di eseguire quel codice quando scrivi un nuovo costruttore.

Mi sto perdendo qualcosa? Ci sono altre situazioni in cui un inizializzatore dovrebbe essere usato? O è davvero solo uno strumento piuttosto limitato da utilizzare in situazioni molto specifiche?

È stato utile?

Soluzione

Gli inizializzatori statici sono utili come detto cletus e li uso allo stesso modo. Se hai una variabile statica che deve essere inizializzata quando la classe viene caricata, allora un inizializzatore statico è la strada da percorrere, soprattutto perché ti consente di effettuare un'inizializzazione complessa e avere ancora la variabile statica final . Questa è una grande vittoria.

Trovo " if (someStaticVar == null) // fare cose " essere disordinato e soggetto a errori. Se viene inizializzato staticamente e dichiarato final , si evita la possibilità che sia null .

Tuttavia, sono confuso quando dici:

  

Gli inizializzatori statici / di istanza possono essere usati per impostare il valore di "finale"   variabili statiche / di istanza mentre un costruttore non può

Presumo che tu stia dicendo entrambi:

  • inizializzatori statici possono essere utilizzati per impostare il valore di "finale" variabili statiche mentre un costruttore non può
  • Gli inizializzatori
  • possono essere utilizzati per impostare il valore di "finale" variabili di istanza mentre un costruttore non può

e hai ragione sul primo punto, sbagliato sul secondo. Ad esempio, puoi farlo:

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

Inoltre, quando un sacco di codice viene condiviso tra costruttori, uno dei modi migliori per gestirlo è quello di concatenare i costruttori, fornendo i valori predefiniti. Questo rende abbastanza chiaro cosa si sta facendo:

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

Altri suggerimenti

Le classi interne anonime non possono avere un costruttore (poiché sono anonime), quindi sono abbastanza naturali per gli inizializzatori dell'istanza.

Uso molto spesso i blocchi di inizializzatore statico per impostare i dati statici finali, in particolare le raccolte. Ad esempio:

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

  ...
}

Ora questo esempio può essere fatto con una singola riga di codice:

private final static List<String> SUITS =
  Collections.unmodifiableList(
    Arrays.asList("Clubs", "Spades", "Hearts", "Diamonds")
  );

ma la versione statica può essere molto più ordinata, in particolare quando gli elementi non sono banali da inizializzare.

Un'implementazione ingenua potrebbe anche non creare un elenco non modificabile, il che è un potenziale errore. Quanto sopra crea una struttura di dati immutabile che puoi felicemente restituire da metodi pubblici e così via.

Solo per aggiungere ad alcuni punti già eccellenti qui. L'inizializzatore statico è thread-safe. Viene eseguito quando la classe viene caricata e quindi rende più semplice l'inizializzazione dei dati statici rispetto all'utilizzo di un costruttore, in cui è necessario un blocco sincronizzato per verificare se i dati statici sono inizializzati e quindi inizializzarli effettivamente.

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

vs

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

Non dimenticare, ora devi eseguire la sincronizzazione a livello di classe, non a livello di istanza. Ciò comporta un costo per ogni istanza costruita anziché un costo una tantum quando la classe viene caricata. Inoltre, è brutto ;-)

Ho letto un intero articolo in cerca di una risposta all'ordine di inizializzazione degli iniziatori rispetto ai loro costruttori. Non l'ho trovato, quindi ho scritto del codice per verificare la mia comprensione. Ho pensato di aggiungere questa piccola dimostrazione come commento. Per verificare la tua comprensione, vedi se riesci a prevedere la risposta prima di leggerla in fondo.

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

}

Output:

java CtorOrder
A ctor
B initX
B ctor

Un inizializzatore statico è l'equivalente di un costruttore nel contesto statico. Lo vedrai sicuramente più spesso di un inizializzatore di istanza. A volte è necessario eseguire il codice per configurare l'ambiente statico.

In generale, un initalizer di istanza è il migliore per le classi interne anonime. Dai un'occhiata a il ricettario di JMock per vedere un modo innovativo di usarlo per rendere il codice più leggibile.

A volte, se hai qualche logica complicata da incatenare tra costruttori (supponiamo che tu stia eseguendo una sottoclasse e non puoi chiamarla () perché devi chiamare super ()), potresti evitare la duplicazione facendo le cose comuni nell'istanza initalizer. Gli inizializzatori di istanze sono così rari, tuttavia, che sono una sintassi sorprendente per molti, quindi li evito e preferirei rendere la mia classe concreta e non anonima se avessi bisogno del comportamento del costruttore.

JMock è un'eccezione, perché è così che deve essere utilizzato il framework.

C'è un aspetto importante che devi considerare nella tua scelta:

I blocchi di inizializzatore sono membri della classe / oggetto, mentre i costruttori non lo sono . Questo è importante quando si considera estensione / sottoclasse :

  1. Gli inizializzatori sono ereditati dalle sottoclassi. (Anche se può essere oscurato)
    Ciò significa che è sostanzialmente garantito che le sottoclassi siano inizializzate come previsto dalla classe genitore.
  2. I costruttori sono non ereditati . (Chiamano solo super () [cioè nessun parametro] implicitamente o devi effettuare manualmente una specifica super (...) .)
    Ciò significa che è possibile che una chiamata super (...) implicita o esplicita non possa inizializzare la sottoclasse come previsto dalla classe genitore.

Considera questo esempio di un blocco di inizializzatore:

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

uscita:
inizializzazione nel blocco inizializzatore di: ChildOfParentWithInitializer init
- > Indipendentemente dai costruttori che implementa la sottoclasse, il campo verrà inizializzato.

Ora considera questo esempio con i costruttori:

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

uscita:
Il costruttore di ChildOfParentWithConstructor viene impostato su null nullo
- > In questo modo il campo verrà inizializzato su null per impostazione predefinita, anche se potrebbe non essere il risultato desiderato.

Vorrei anche aggiungere un punto insieme a tutte le favolose risposte di cui sopra. Quando cariciamo un driver in JDBC utilizzando Class.forName ("quot"), si verifica il caricamento della classe e l'inizializzatore statico della classe Driver viene attivato e il codice al suo interno registra Driver in Driver Manager. Questo è uno degli usi significativi del blocco di codice statico.

Come hai detto, non è utile in molti casi e come con qualsiasi sintassi meno utilizzata, probabilmente vorrai evitarlo solo per impedire alla persona successiva che guarda il tuo codice di spendere i 30 secondi per estrarlo le volte.

D'altra parte, è l'unico modo per fare alcune cose (penso che tu le abbia praticamente coperte).

Le stesse variabili statiche dovrebbero essere comunque evitate in qualche modo - non sempre, ma se ne usi molte o se ne usi molte in una classe, potresti trovare approcci diversi, il tuo io futuro ti ringrazierà.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top