2015-05-12 9 views
33

Recentemente mi sono imbattuto in questo lavoro. Anche se non sono sicuro che sia davvero una buona idea, non capisco come i blocchi statici siano gestiti dal compilatore.Come vengono risolte le dipendenze tra i blocchi statici tra oggetti?

Ecco un esempio:

Si consideri che avete classi A e B:

public class A { 

    public final static List<Integer> list; 
    static { 
     list = new ArrayList<>(); 
    } 
} 

public class B { 

    public final static int dependsOnA; 
    static { 
     dependsOnA = A.list.size(); 
    } 
} 

E una classe principale che legge solo B.dependsOnA.

Il blocco statico in B dipende da quello in A, poiché utilizza la variabile statica list.

Ora, il codice viene eseguito correttamente e nessun NullPointerException viene generato in fase di esecuzione. Ma qual è il meccanismo che assicura che list sia inizializzato prima che sia potenzialmente utilizzato altrove?

+3

Da un punto di vista * design *, suggerisco caldamente che non è una buona idea per la classe 'A' rendere * public * la sua proprietà' list', in primo luogo. Invece, l'avrei reso privato e fornito * metodi * in 'A' che forniscono a tutte le altre classi" le cose che devono sapere e la capacità di fare le cose che devono fare ". Un vantaggio molto importante è che, ora, la classe 'A' è sia autonoma che auto-descrittiva. Tutto il codice necessario per garantire che la classe 'A' funzioni correttamente, è * in * class' A', il cui codice è protetto da interferenze o effetti collaterali (come 'B'). –

+1

... anche: "Cosa può fare' A' e cosa possono fare gli altri per 'A'?" A questa domanda può ora essere data una risposta, in via definitiva, guardando * solo * a 'A'. La differenza di runtime tra i due è trascurabile, ma il design, IMHO, è molto più robusto e debugabile. (E, molto meno dipendente da "esattamente quando Java fa questo o quello".) Sottoscrivo che è un buon principio dire: "non lasciare che le altre classi si intromettano negli affari di altre classi". Per quanto sia saggio per gli umani ("che sono croccanti e gustosi con i fagioli Fava ...") non per intromettersi con i draghi. –

+0

se la lista è già statica wouldnt A.list.size() l'accesso diretto è più efficiente? –

risposta

8

Durante l'esecuzione del blocco di staticB, il runtime incontra A per la prima volta, e richiamerà il static blocco di A prima di accedere A.list.

+1

Il termine blocchi 'static' è più comune del costruttore' static'. Non sono sicuro che qualcuno usi effettivamente questo termine. – CKing

+1

Grazie Chetan, l'ho cambiato ora. Uso molti altri linguaggi (ad es. C#) in cui è chiamato costruttore statico. – Glorfindel

7

Indipendentemente da come si scrive il codice, un blocco static è un blocco static e verrà eseguito come parte della JVM che carica una classe.

Quando si dice B.dependsOnA, B La lezione inizia geting caricato dalla JVM e il blocco static in B viene chiamato da qualche parte durante questo processo. Quando dici dependsOnA = A.list.size();, la classe A inizia a essere caricata dalla JVM e il blocco static in A verrà eseguito da qualche parte durante questo processo che inizializza lo list. L'articolo list.size() verrà eseguito solo dopo che la classe A è stata caricata completamente dalla JVM. Successivamente, la JVM può solo terminare il caricamento della classe B dopo il completamento del blocco statico in B.

9

Il "meccanismo" è il classloader della JVM, che garantirà l'esecuzione di blocchi di inizializzazione di classe (con un blocco globale sull'intera JVM) prima di restituire il flusso di controllo al punto di riferimento della classe. Caricherà prima la classe A solo dopo aver fatto riferimento, in questo caso quando il blocco di init di B fa riferimento a A.list.

5

Qui abbiamo alcune spiegazione Static Block in Java

Se si chiama la A di prima classe, la A statica viene chiamato e A.list esiste e quando B si chiamano.

Se si chiama prima la classe B, viene chiamato il B statico, in cascata alla chiamata A, chiamando il suo blocco statico, dove viene creato A.list.

Abbiamo potuto vedere è il suo senso più delicata: B> B.static> A> A.static> A.list esiste

4

Il funzionamento è molto semplice caricatore di classi JVM, che farà sì che una classe statica i blocchi vengono eseguiti quando la classe viene prima referenziata.
1.Se si dispone di istruzioni eseguibili nel blocco statico, JVM eseguirà automaticamente queste istruzioni quando la classe viene caricata in JVM.
2.Se stai facendo riferimento ad alcune variabili/metodi statici dai blocchi statici, queste istruzioni verranno eseguite dopo che la classe è stata caricata in JVM come sopra, cioè ora le variabili statiche/metodi riferiti e il blocco statico saranno entrambi eseguito.

34

Il meccanismo è descritto in dettaglio here, ma i cinque punti più importanti sono:

  1. Prima che una classe viene fatto riferimento, deve essere inizializzato.
  2. Se l'inizializzazione di una classe è già iniziata (o se è stata completata), non viene tentata di nuovo.
  3. Prima di inizializzare una classe, tutte le superclassi e le superinterfacce devono essere inizializzate per prime.
  4. Le inizializzazioni statiche all'interno di una singola classe vengono eseguite in ordine testuale.
  5. Le interfacce implementate vengono inizializzate nell'ordine in cui sono visualizzate nella clausola implements.

Queste regole definiscono completamente l'ordine in cui vengono eseguiti i blocchi statici.

Il tuo caso è piuttosto semplice: prima di accedere a B.dependsOnA, B ha bisogno di essere inizializzato (regola 1), l'initialiser statica viene poi tentando di accedere A.list, che innesca l'inizializzazione della classe A (ancora una volta regola 1).

Nota che non c'è nulla ti impedisce di creare dipendenze circolari in questo modo, che farà sì che le cose interessanti da accadere:

public class Bar { 
    public static int X = Foo.X+1; 

    public static void main(String[] args) { 
     System.out.println(Bar.X+" "+Foo.X); // 
    } 

} 

class Foo { 
    public static int X = Bar.X+1; 
} 

Il risultato qui è 2 1 perché il modo in cui l'inizializzazione accade è questo:

  1. Bar inizia l'inizializzazione.
  2. Bar.X s valore iniziale viene valutata, che richiede inizializzazione Foo primo
  3. Foo s inizializzazione comincia.
  4. Foo.X s valore iniziale viene valutata, ma poiché Bar s inizializzazione è già in corso, non viene inizializzata di nuovo, Bar.X s valore "corrente" viene utilizzato, che è 0, quindi Foo.X è inizializzato a 1.
  5. siamo di nuovo alla valutazione Bar.X s valore, Foo.X è 1 in modo Bar.X diventa 2.

Questo funziona anche se entrambi i campi sono stati dichiarati final.

La morale della storia è fare attenzione con inizializzatori statici che fanno riferimento ad altre classi nella stessa libreria o applicazione (fare riferimento alle classi di una libreria di terze parti o di una libreria di classi standard è sicura in quanto non si riferiranno a la tua classe).

6

Questo è il compito del caricatore classe.Il caricamento della classe in java inizia con il bootloader bootstrap. Questo caricatore di classi carica prima tutte le classi nella libreria java standard, lo rt.jar.

Quindi viene richiamato il programma di caricamento classe . Carica tutte le classi dai file jar di estensione installati in una directory ext JVM. Ora finalmente viene richiamato il classloader del classpath.

Il classloader del percorso classe inizia a caricare le classi dalla classe principale, la classe che ha il metodo principale definito. Una volta caricato, esegue qualsiasi inizializzatore statico in quella classe. Durante l'esecuzione dell'inizializzatore, se incontra una classe che non è caricata, interrompe l'esecuzione del blocco statico, carica prima la classe e infine riprende l'esecuzione di quel blocco statico.

Pertanto, non è possibile che si verifichino chiamate a classi non caricate. Vediamo questo con il vostro esempio, il cui codice è simile a questo:

class A 
{ 
    public final static List<Integer> list; 
    static 
    { 
     System.out.println("Loaded Class A"); 
     list = new ArrayList<>(); 
    } 
} 

class B 
{ 
    public final static int dependsOnA; 
    static 
    { 
     System.out.println("Loaded Class B"); 
     dependsOnA = A.list.size(); 
    } 
} 

Qui, in questo esempio, non v'è in realtà alcun metodo principale, in modo queste classi non verranno effettivamente caricati nella memoria. Supponiamo, aggiungiamo la seguente classe principale al codice precedente.

class C 
{ 
    static 
    { 
     System.out.println("Loaded Class C"); 
    } 

    public static void main(String[] args) 
    { 
     System.out.println(B.dependsOnA); 
    } 
} 

Vediamo cosa questo produrrebbe nell'output: http://ideone.com/pLg3Uh

Loaded Class C 
Loaded Class B 
Loaded Class A 
0 

Cioè, prima della classe C è caricato, perché aveva il metodo principale. Una volta caricato, viene richiamato l'inizializzatore statico della classe C. Si noti, tuttavia, che il metodo principale viene richiamato dopo il caricamento del blocco statico della classe C.

Ora il metodo principale, abbiamo stampato il valore di dependsOnA di classe B. Ora, il caricatore di classe interrompe l'esecuzione questa affermazione, e carica la classe B, e lo esegue è blocco statico, che a sua volta, assegna la variabile dependsOnA con il valore del numero di elementi nell'elenco di classe A, che non è caricato.

Quindi il caricatore di classi salta da lì, carica la classe ora e richiama il blocco statico della classe A e viene creato un elenco. Ora dal momento che non ci sono più classi da caricare, il programma di caricamento classi ritorna al blocco statico di classe B e l'assegnazione è completa. Ora finalmente, il controllo è ora con il metodo principale e il valore di dependsOnA viene stampato sulla console.

Spero che questo aiuti.

Problemi correlati