Uso di Initializers vs Costruttori in Java

Quindi ho aggiornato le mie competenze Java e ho trovato alcune funzionalità che non sapevo in precedenza. Gli inizializzatori statici e di istanza sono due di tali tecniche.

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

  • gli inizializzatori statici / di istanza possono essere utilizzati per impostare il valore di variabili statiche / di istanza “finali” mentre un costruttore non può

  • gli inizializzatori statici possono essere utilizzati per impostare il valore di qualsiasi variabile statica in una class, che dovrebbe essere più efficiente di un blocco di codice “if (someStaticVar == null) // do stuff” all’inizio di ogni costruttore

Entrambi questi casi presuppongono che il codice richiesto per impostare queste variabili sia più complesso del semplice “var = value”, poiché altrimenti non sembrerebbe esserci alcun motivo per utilizzare un inizializzatore invece di impostare semplicemente il valore quando si dichiara la variabile.

Tuttavia, mentre questi non sono 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 un sacco di ciò che viene fatto in un costruttore, ma non vedo davvero il motivo per farlo. Anche se tutti i costruttori di una class condividono una grande quantità di codice, l’uso di una funzione di inizializzazione privata () sembra avere più senso per me che usare un inizializzatore perché non ti blocca nell’avere quel codice eseguito quando scrivi un nuovo costruttore.

Mi sto perdendo qualcosa? Esistono molte altre situazioni in cui è necessario utilizzare un inizializzatore? O è davvero solo uno strumento piuttosto limitato da utilizzare in situazioni molto specifiche?

Gli inizializzatori statici sono utili come menzionato da Cletus e li uso allo stesso modo. Se si dispone di una variabile statica che deve essere inizializzata quando viene caricata la class, un inizializzatore statico è la strada da percorrere, soprattutto in quanto consente di eseguire un’inizializzazione complessa e mantenere la variabile statica final . Questa è una grande vittoria.

Trovo che “if (someStaticVar == null) // do stuff” sia disordinato e sobject a errori. Se è inizializzato staticamente e dichiarato final , si evita la possibilità che sia null .

Tuttavia, sono confuso quando dici:

gli inizializzatori statici / di istanza possono essere utilizzati per impostare il valore di variabili statiche / di istanza “finali” mentre un costruttore non può

Presumo che tu stia dicendo entrambi:

  • gli inizializzatori statici possono essere utilizzati per impostare il valore delle variabili statiche “finali” mentre un costruttore non può
  • gli inizializzatori di istanze possono essere utilizzati per impostare il valore delle variabili di istanza “finali” mentre un costruttore non può

e tu sei corretto sul primo punto, sbagliato sul secondo. Ad esempio, puoi fare questo:

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

Inoltre, quando un sacco di codice è condiviso tra costruttori, uno dei modi migliori per gestirlo è quello di concatenare i costruttori, fornendo i valori predefiniti. Questo rende abbastanza chiaro ciò che viene fatto:

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

Le classi interne anonime non possono avere un costruttore (dato che sono anonimi), quindi sono un adattamento naturale per gli inizializzatori di istanza.

Il più delle volte utilizzo i blocchi di inizializzazione statici per impostare i dati statici finali, in particolare le raccolte. Per esempio:

 public class Deck { private final static List SUITS; static { List list = new ArrayList(); 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 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, che è un potenziale errore. Quanto sopra crea una struttura dati immutabile che è ansible tornare felicemente dai metodi pubblici e così via.

Solo per aggiungere alcuni punti già eccellenti qui. L’inizializzatore statico è thread-safe. Viene eseguito quando viene caricata la class e ciò rende più semplice l’inizializzazione dei dati statici rispetto all’utilizzo di un costruttore, nel quale è necessario un blocco sincronizzato per verificare se i dati statici vengono inizializzati e quindi effettivamente inizializzati.

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

contro

 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 sincronizzare la class, non il livello di istanza. Ciò comporta un costo per ogni istanza costruita invece di un costo una tantum quando viene caricata la class. Inoltre, è brutto 😉

Ho letto un intero articolo alla ricerca di una risposta all’ordine di inizializzazione degli inizializzatori rispetto ai loro costruttori. Non l’ho trovato, quindi ho scritto un codice per verificare la mia comprensione. Ho pensato di aggiungere questa piccola dimostrazione come commento. Per verificare la tua comprensione, vedi se puoi 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"); } } 

Produzione:

 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 al ricettario di JMock per vedere un modo innovativo di usarlo per rendere il codice più leggibile.

A volte, se hai una logica complicata da concatenare tra costruttori (diciamo che stai sottoclassi e non puoi chiamare questo () perché devi chiamare super ()), potresti evitare la duplicazione facendo le cose comuni nell’istanza initalizer. Gli initalizzatori di istanza sono così rari, tuttavia, che sono una syntax sorprendente per molti, quindi li evito e preferirei rendere la mia class concreta e non anonima se ho bisogno del comportamento del costruttore.

JMock è un’eccezione, perché è così che si intende utilizzare il framework.

Vorrei anche aggiungere un punto insieme a tutte le risposte favolose di cui sopra. Quando cariciamo un driver in JDBC utilizzando Class.forName (“”), il caricamento della class avviene e l’inizializzatore statico della class Driver viene generato 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 syntax meno utilizzata, probabilmente vuoi evitarlo solo per impedire alla prossima persona che guarda il tuo codice di spendere i 30 secondi per estrarla dai depositi.

D’altra parte, è l’unico modo per fare alcune cose (penso che tu abbia praticamente coperto quelle).

Le variabili statiche stesse dovrebbero comunque essere in qualche modo evitate – non sempre, ma se ne usate molte o se usate molto in una class, potreste trovare approcci diversi, il vostro sé futuro vi ringrazierà.

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

I blocchi di inizializzazione sono membri della class / object, mentre i costruttori no . Questo è importante quando si prende in considerazione l’ estensione / sottoclass :

  1. Gli inizializzatori sono ereditati da sottoclassi. (Anche se può essere ombreggiato)
    Ciò significa che è sostanzialmente garantito che le sottoclassi sono inizializzate come previsto dalla class genitore.
  2. I costruttori non sono ereditati , però. (Chiamano implicitamente super() [cioè senza parametri] o devi effettuare manualmente una specifica super(...) chiamata.)
    Ciò significa che è ansible che una chiamata super(...) implicita o esplicita super(...) non inizializzi la sottoclass come previsto dalla class genitore.

Considera questo esempio di un blocco di inizializzazione:

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

produzione:
initializing in initializer block of: ChildOfParentWithInitializer init
-> Indipendentemente dai costruttori che la sottoclass implementa, il campo verrà inizializzato.

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

produzione:
Constructor of ChildOfParentWithConstructor inits to null null
-> In questo modo il campo verrà inizializzato come predefinito, anche se potrebbe non essere il risultato desiderato.