Blocchi di inizializzazione statici


265

Per quanto ho capito, il "blocco di inizializzazione statica" viene utilizzato per impostare i valori del campo statico se non può essere eseguito su una riga.

Ma non capisco perché abbiamo bisogno di un blocco speciale per questo. Ad esempio dichiariamo un campo come statico (senza assegnazione di valore). Quindi scrivere diverse righe del codice che generano e assegnano un valore al campo statico sopra dichiarato.

Perché abbiamo bisogno di queste righe in un blocco speciale come static {...}:?


6
Feedback minore, ma sarebbe utile se potessi per favore dichiarare chiaramente i tuoi presupposti, e quindi chiarire quale risposta è corretta. Quando ho letto la tua domanda, i mis-capito e di pensiero si sapeva la differenza tra {...}vs static {...}. (nel qual caso Jon Skeet ha sicuramente risposto alla tua domanda molto meglio)
David T.

1
Questa domanda non è molto chiara; hai i soccorritori che si arrampicano e fanno molte congetture prolissi su ciò che intendevi. Che ne dici di scrivere esplicitamente il blocco di inizializzazione statica che hai in mente e la tua alternativa, in modo che le persone abbiano qualcosa di chiaro a cui rispondere?
Don Hatch,

Risposte:


430

Il blocco non statico:

{
    // Do Something...
}

Viene chiamato ogni volta che viene costruita un'istanza della classe. Il blocco statico viene chiamato solo una volta , quando la classe stessa viene inizializzata, indipendentemente dal numero di oggetti di quel tipo che crei.

Esempio:

public class Test {

    static{
        System.out.println("Static");
    }

    {
        System.out.println("Non-static block");
    }

    public static void main(String[] args) {
        Test t = new Test();
        Test t2 = new Test();
    }
}

Questo stampa:

Static
Non-static block
Non-static block

107
Perché questa è la risposta accettata? Non risponde nemmeno alla domanda.
Paul Bellora,

43
Risponde alla domanda: "Questo viene chiamato ogni volta che viene costruita la classe. Il blocco statico viene chiamato solo una volta, indipendentemente dal numero di oggetti di quel tipo che crei."
Adam Arold,

83
Per il lettore curioso, il blocco non statico viene effettivamente copiato dal compilatore Java in ogni costruttore della classe ( sorgente ). Quindi è ancora compito del costruttore inizializzare i campi.
Martin Andersson,

2
La risposta accettata dovrebbe essere questa: stackoverflow.com/a/2420404/363573 . Questa risposta presenta un esempio di vita reale in cui sono necessari blocchi statici.
Stephan,

16
Perché questa risposta viene improvvisamente ridimensionata? Potresti non essere d'accordo sul fatto che questa sia la risposta accettata, ma certamente non è in alcun modo sbagliata o fuorviante. Sta semplicemente cercando di aiutare la comprensione di questi costrutti del linguaggio con un semplice esempio.
Frederik Wordenskjold,

133

Se non fossero in un blocco di inizializzazione statica, dove sarebbero? Come dichiareresti una variabile che doveva essere locale solo ai fini dell'inizializzazione e distinguerla da un campo? Ad esempio, come si desidera scrivere:

public class Foo {
    private static final int widgets;

    static {
        int first = Widgets.getFirstCount();
        int second = Widgets.getSecondCount();
        // Imagine more complex logic here which really used first/second
        widgets = first + second;
    }
}

Se firste secondnon fossero in un blocco, sembrerebbero campi. Se si trovassero in un blocco senza staticdi esso, ciò comporterebbe un blocco di inizializzazione dell'istanza anziché un blocco di inizializzazione statico, quindi verrebbe eseguito una volta per istanza costruita anziché una volta in totale.

Ora in questo caso particolare, è possibile utilizzare invece un metodo statico:

public class Foo {
    private static final int widgets = getWidgets();

    static int getWidgets() {
        int first = Widgets.getFirstCount();
        int second = Widgets.getSecondCount();
        // Imagine more complex logic here which really used first/second
        return first + second;
    }
}

... ma ciò non funziona quando ci sono più variabili che si desidera assegnare all'interno dello stesso blocco o nessuna (ad es. se si desidera semplicemente registrare qualcosa o inizializzare una libreria nativa).


1
Il blocco statico si verifica prima o dopo l'assegnazione delle variabili statiche? private static int widgets = 0; static{widgets = 2;}
Weishi Zeng,

1
Era curioso di sapere se il blocco statico si verifica prima o dopo l'assegnazione delle variabili statiche. ad esempio, abbiamo private static int widgets = 0; static{widgets = 2;}scoperto che l'assegnazione '=' avviene in ordine, il che significa che prima viene assegnato '='. L'esempio di cui sopra darà a "widget" un valore di 2. (PS non sapeva che i commenti possono essere modificati solo in 5 minuti ...)
Weishi Zeng,

@WeishiZeng: Sì, è documentato in docs.oracle.com/javase/specs/jls/se8/html/… - punto 9.
Jon Skeet,

Ma non potresti anche utilizzare un metodo statico privato che ha lo stesso codice del blocco di inizializzazione statica e assegnare i widget al metodo statico privato?
Zachary Kraus,

1
@Zachary: intendi restituire il valore e assegnare il risultato della chiamata del metodo? Se è così, sì - quando si sta assegnando ad esattamente una variabile a causa del blocco. Modificherò la mia risposta con i dettagli in circa 7 ore ...
Jon Skeet,

103

Ecco un esempio:

  private static final HashMap<String, String> MAP = new HashMap<String, String>();
  static {
    MAP.put("banana", "honey");
    MAP.put("peanut butter", "jelly");
    MAP.put("rice", "beans");
  }

Il codice nelle sezioni "statiche" verrà eseguito al momento del caricamento della classe, prima che vengano costruite tutte le istanze della classe (e prima che vengano chiamati altri metodi statici altrove). In questo modo puoi assicurarti che le risorse della classe siano tutte pronte per l'uso.

È anche possibile avere blocchi di inizializzazione non statici. Quelli si comportano come estensioni all'insieme di metodi di costruzione definiti per la classe. Sembrano blocchi di inizializzazione statici, tranne la parola chiave "statica" viene lasciata fuori.


4
Per quel particolare esempio, a volte il modello a doppio controvento è stato "abusato" :)
BalusC

Può essere maltrattato, ma d'altro canto pulisce alcuni pasticci e rende alcuni tipi di codice un po 'più "solidi". Programma a Erlang per divertimento, e ti appassiona non aver bisogno di variabili locali :-)
Pointy

1
<< Il codice nelle sezioni "statiche" verrà eseguito al momento del caricamento della classe, prima che vengano costruite tutte le istanze della classe (e prima che vengano chiamati altri metodi statici altrove). In questo modo puoi assicurarti che le risorse della classe siano tutte pronte per l'uso. >> (Quale "punta" menzionato nella risposta sopra) questo è un punto molto importante da notare quando si tratta di eseguire blocchi statici.
discente il

Possiamo farlo con l'utilizzo di InitializingBean nel metodo after afterPropertiesSet?
egemen,

48

È anche utile quando in realtà non si desidera assegnare il valore a nulla, come caricare una classe una sola volta durante il runtime.

Per esempio

static {
    try {
        Class.forName("com.example.jdbc.Driver");
    } catch (ClassNotFoundException e) {
        throw new ExceptionInInitializerError("Cannot load JDBC driver.", e);
    }
}

Ehi, c'è un altro vantaggio, puoi usarlo per gestire le eccezioni. Immagina che getStuff()qui venga lanciato un oggetto Exceptionche appartiene davvero a un blocco catch:

private static Object stuff = getStuff(); // Won't compile: unhandled exception.

quindi un staticinizializzatore è utile qui. È possibile gestire l'eccezione lì.

Un altro esempio è fare cose in seguito che non possono essere fatte durante l'assegnazione:

private static Properties config = new Properties();

static {
    try { 
        config.load(Thread.currentThread().getClassLoader().getResourceAsStream("config.properties");
    } catch (IOException e) {
        throw new ExceptionInInitializerError("Cannot load properties file.", e);
    }
}

Per tornare all'esempio del driver JDBC, qualsiasi driver JDBC decente utilizza anche l' staticinizializzatore per registrarsi in DriverManager. Vedi anche questa e questa risposta.


2
Qui sta il pericoloso voodoo ... gli inizializzatori statici vengono eseguiti nel metodo clinit () sintetico, che è implicitamente sincronizzato . Ciò significa che JVM acquisirà un blocco sul file di classe in questione. Ciò può portare a deadlock in ambienti multithread se due classi tentano di caricarsi a vicenda e ognuna inizia a caricarsi in un thread diverso. Vedi www-01.ibm.com/support/docview.wss?uid=swg1IV48872
Ajax,

@Ajax: lo considererei un bug nel driver JDBC in questione o nel codice dell'applicazione responsabile del caricamento. Di solito, nel caso di driver JDBC decenti, purché lo si carichi solo una volta a livello di applicazione durante l'avvio dell'applicazione, non c'è nulla in questione.
BalusC,

Sarebbe certamente un bug, ma non del tutto colpa del driver JDBC. Forse il driver ha innocentemente i suoi inizializzatori statici e forse inizializzi innocentemente questa classe insieme ad alcuni altri nella tua app e, oh no, alcune classi inaspettate si caricano ciclicamente a vicenda e ora i deadlock delle tue app. L'ho scoperto grazie allo stallo tra java.awt.AWTEvent e sun.util.logging.PlatformLogger. Ho toccato solo AWTEvent per dirgli di funzionare senza testa, e qualche altra lib ha finito col caricare PlatformLogger ... che anche AWTEvent carica.
Ajax,

1
Entrambe le classi sono finite sincronizzate su thread diversi e la mia build ha avuto un deadlock di circa 1/150 corse. Quindi, ora sono molto più attento al caricamento delle classi in blocchi statici. Nel caso sopra menzionato, utilizzando un modello di provider differito in cui è possibile creare immediatamente una classe di provider temporanea (senza possibilità di deadlock), inizializzare il campo e quindi quando si accede effettivamente (in un accesso al campo non sincronizzato), quindi carico effettivamente le classi che possono causare il deadlock.
Ajax,

11

Direi che static blockè solo zucchero sintattico. Non c'è niente che tu possa fare con staticBlock e non con nient'altro.

Per riutilizzare alcuni esempi pubblicati qui.

Questo pezzo di codice può essere riscritto senza usare l' staticinizializzatore.

Metodo n. 1: con static

private static final HashMap<String, String> MAP;
static {
    MAP.put("banana", "honey");
    MAP.put("peanut butter", "jelly");
    MAP.put("rice", "beans");
  }

Metodo n. 2: senza static

private static final HashMap<String, String> MAP = getMap();
private static HashMap<String, String> getMap()
{
    HashMap<String, String> ret = new HashMap<>();
    ret.put("banana", "honey");
    ret.put("peanut butter", "jelly");
    ret.put("rice", "beans");
    return ret;
}

10

Esistono alcuni motivi reali per cui è necessario esistere:

  1. inizializzazione di static finalmembri la cui inizializzazione potrebbe generare un'eccezione
  2. inizializzazione dei static finalmembri con valori calcolati

Le persone tendono a usare i static {}blocchi come un modo conveniente per inizializzare cose da cui la classe dipende anche durante il runtime, come ad esempio assicurare che una particolare classe sia caricata (ad esempio, driver JDBC). Ciò può essere fatto in altri modi; tuttavia, le due cose che menziono sopra possono essere fatte solo con un costrutto come il static {}blocco.


8

È possibile eseguire bit di codice una volta per una classe prima che un oggetto venga creato nei blocchi statici.

Per esempio

class A {
  static int var1 = 6;
  static int var2 = 9;
  static int var3;
  static long var4;

  static Date date1;
  static Date date2;

  static {
    date1 = new Date();

    for(int cnt = 0; cnt < var2; cnt++){
      var3 += var1;
    }

    System.out.println("End first static init: " + new Date());
  }
}

7

È un'idea sbagliata comune pensare che un blocco statico abbia accesso solo ai campi statici. Per questo vorrei mostrare sotto un pezzo di codice che uso abbastanza spesso in progetti di vita reale (copiato parzialmente da un'altra risposta in un contesto leggermente diverso):

public enum Language { 
  ENGLISH("eng", "en", "en_GB", "en_US"),   
  GERMAN("de", "ge"),   
  CROATIAN("hr", "cro"),   
  RUSSIAN("ru"),
  BELGIAN("be",";-)");

  static final private Map<String,Language> ALIAS_MAP = new HashMap<String,Language>(); 
  static { 
    for (Language l:Language.values()) { 
      // ignoring the case by normalizing to uppercase
      ALIAS_MAP.put(l.name().toUpperCase(),l); 
      for (String alias:l.aliases) ALIAS_MAP.put(alias.toUpperCase(),l); 
    } 
  } 

  static public boolean has(String value) { 
    // ignoring the case by normalizing to uppercase
    return ALIAS_MAP.containsKey(value.toUpper()); 
  } 

  static public Language fromString(String value) { 
    if (value == null) throw new NullPointerException("alias null"); 
    Language l = ALIAS_MAP.get(value); 
    if (l == null) throw new IllegalArgumentException("Not an alias: "+value); 
    return l; 
  } 

  private List<String> aliases; 
  private Language(String... aliases) { 
    this.aliases = Arrays.asList(aliases); 
  } 
} 

Qui l'inizializzatore viene utilizzato per mantenere un indice ( ALIAS_MAP), per mappare un set di alias al tipo di enum originale. È inteso come estensione del metodo valueOf incorporato fornito dallo Enumstesso.

Come puoi vedere, l'inizializzatore statico accede anche al privatecampo aliases. È importante capire che il staticblocco ha già accesso alle Enumistanze del valore (ad es ENGLISH.). Questo perché l' ordine di inizializzazione ed esecuzione nel caso dei Enumtipi , proprio come se i static privatecampi fossero stati inizializzati con istanze prima che i staticblocchi fossero stati chiamati:

  1. Le Enumcostanti che sono campi statici impliciti. Ciò richiede il costruttore Enum e i blocchi di istanza e anche l'inizializzazione dell'istanza deve avvenire per prima.
  2. static blocco e inizializzazione di campi statici nell'ordine in cui si verificano.

Questa inizializzazione fuori ordine (costruttore prima del staticblocco) è importante da notare. Succede anche quando inizializziamo i campi statici con le istanze in modo simile a un Singleton (semplificazioni apportate):

public class Foo {
  static { System.out.println("Static Block 1"); }
  public static final Foo FOO = new Foo();
  static { System.out.println("Static Block 2"); }
  public Foo() { System.out.println("Constructor"); }
  static public void main(String p[]) {
    System.out.println("In Main");
    new Foo();
  }
}

Quello che vediamo è il seguente output:

Static Block 1
Constructor
Static Block 2
In Main
Constructor

È chiaro che l'inizializzazione statica può effettivamente avvenire prima del costruttore e anche dopo:

Il semplice accesso a Foo nel metodo principale provoca il caricamento della classe e l'avvio dell'inizializzazione statica. Ma come parte dell'inizializzazione statica chiamiamo nuovamente i costruttori per i campi statici, dopo di che riprende l'inizializzazione statica e completa il costruttore chiamato dall'interno del metodo principale. Situazione piuttosto complessa per la quale spero che nella normale codifica non dovremmo affrontare.

Per maggiori informazioni a riguardo vedi il libro " Efficace Java ".


1
L'accesso a aliasesnon significa che il blocco statico può accedere a membri non statici. aliasessi accede attraverso i Languagevalori restituiti dal values()metodo / static / . Come hai detto, il fatto che le variabili enum siano già disponibili a quel punto è l'insolito bit - i membri non statici delle classi regolari non sarebbero accessibili in questa situazione.
Ignazio,

Il blocco statico sta ancora accedendo solo ai campi statici (nel caso del tuo enum INGLESE, TEDESCO, ...) che in questo caso sono oggetti. Poiché i campi statici sono oggetti stessi, è possibile accedere al campo dell'istanza dell'oggetto statico.
Swami PR,

1
class Foo { static final Foo Inst1; static final Foo Inst2; static{ Inst1 = new Foo("Inst1"); Inst2 = new Foo("Inst2"); } static { System.out.println("Inst1: " + Inst1.member); System.out.println("Inst2: " + Inst2.member); } private final String member; private Foo(String member){ this.member = member; } } Il codice sopra riportato non è diverso dall'esempio enum e consente comunque l'accesso alla variabile di istanza all'interno del blocco statico
Swami PR

@SwamiPR in effetti si compila, con mia sorpresa, e devo ammettere che il codice non è in linea di principio diverso. Devo rileggere le specifiche Java, sento che c'è qualcosa che mi è sfuggito. Buona risposta, grazie.
YoYo

@SwamiPR Il problema è che dovremmo usare un Enum. È il modo migliore per garantire che stiamo puntando a casi singolari '- vedi qui . E ai tuoi punti, ho fatto diversi aggiornamenti.
YoYo

3

Se è necessario impostare le variabili statiche in fase di esecuzione, un static {...}blocco è molto utile.

Ad esempio, se è necessario impostare il membro statico su un valore memorizzato in un file di configurazione o database.

Utile anche quando si desidera aggiungere valori a un Mapmembro statico poiché non è possibile aggiungere questi valori nella dichiarazione del membro iniziale.


3

Quindi hai un campo statico (chiamato anche "variabile di classe" perché appartiene alla classe piuttosto che a un'istanza della classe; in altre parole è associato alla classe piuttosto che a qualsiasi oggetto) e vuoi inizializzarlo. Quindi, se NON vuoi creare un'istanza di questa classe e vuoi manipolare questo campo statico, puoi farlo in tre modi:

1- Basta inizializzarlo quando si dichiara la variabile:

static int x = 3;

2- Avere un blocco di inizializzazione statico:

static int x;

static {
 x=3;
}

3- Avere un metodo di classe (metodo statico) che accede alla variabile di classe e la inizializzi: questa è l'alternativa al blocco statico sopra; puoi scrivere un metodo statico privato:

public static int x=initializeX();

private static int initializeX(){
 return 3;
}

Ora perché dovresti usare il blocco di inizializzazione statica invece dei metodi statici?

Dipende davvero da quello che ti serve nel tuo programma. Ma devi sapere che il blocco di inizializzazione statico viene chiamato una volta e l'unico vantaggio del metodo class è che possono essere riutilizzati in seguito se è necessario reinizializzare la variabile di classe.

diciamo che hai un array complesso nel tuo programma. Lo inizializzi (usando ad esempio il ciclo for) e poi i valori in questo array cambieranno durante il programma ma poi ad un certo punto vuoi reinizializzarlo (tornare al valore iniziale). In questo caso è possibile chiamare il metodo statico privato. Nel caso in cui non sia necessario reinizializzare i valori nel programma, è possibile utilizzare semplicemente il blocco statico e non è necessario un metodo statico poiché non lo si utilizzerà più avanti nel programma.

Nota: i blocchi statici vengono chiamati nell'ordine in cui compaiono nel codice.

Esempio 1:

class A{
 public static int a =f();

// this is a static method
 private static int f(){
  return 3;
 }

// this is a static block
 static {
  a=5;
 }

 public static void main(String args[]) {
// As I mentioned, you do not need to create an instance of the class to use the class variable
  System.out.print(A.a); // this will print 5
 }

}

Esempio 2:

class A{
 static {
  a=5;
 }
 public static int a =f();

 private static int f(){
  return 3;
 }

 public static void main(String args[]) {
  System.out.print(A.a); // this will print 3
 }

}

0

Come supplementare, come ha detto @Pointy

Il codice nelle sezioni "statiche" verrà eseguito al momento del caricamento della classe, prima che vengano costruite tutte le istanze della classe (e prima che vengano chiamati altri metodi statici altrove).

Dovrebbe aggiungersi System.loadLibrary("I_am_native_library")nel blocco statico.

static{
    System.loadLibrary("I_am_a_library");
}

Garantirà che nessun metodo nativo venga chiamato prima che la relativa libreria venga caricata in memoria.

Secondo loadLibrary dall'oracolo :

Se questo metodo viene chiamato più di una volta con lo stesso nome di libreria, la seconda e le successive chiamate vengono ignorate.

Quindi, inaspettatamente, mettere System.loadLibrary non viene usato per evitare che la libreria venga caricata più volte.


0

È innanzitutto necessario comprendere che le classi delle applicazioni stesse vengono istanziate sugli java.class.Classoggetti durante il runtime. Questo è quando vengono eseguiti i blocchi statici. Quindi puoi effettivamente farlo:

public class Main {

    private static int myInt;

    static {
        myInt = 1;
        System.out.println("myInt is 1");
    }

    //  needed only to run this class
    public static void main(String[] args) {
    }

}

e stampa "myInt is 1" sulla console. Nota che non ho istanziato nessuna classe.


0
static int B,H;
static boolean flag = true;
static{
    Scanner scan = new Scanner(System.in);
    B = scan.nextInt();
    scan.nextLine();
    H = scan.nextInt();

    if(B < 0 || H < 0){
        flag = false;
        System.out.println("java.lang.Exception: Breadth and height must be positive");
    } 
}

-1

il blocco statico viene utilizzato per qualsiasi tecnologia per inizializzare un membro di dati statici in modo dinamico, oppure possiamo dire per l'inizializzazione dinamica del blocco statico di un membro di dati statici in uso .. Perché per l'inizializzazione di un membro di dati non statico abbiamo costruttore ma non abbiamo qualsiasi luogo in cui possiamo inizializzare dinamicamente un membro con dati statici

Eg:-class Solution{
         // static int x=10;
           static int x;
       static{
        try{
          x=System.out.println();
          }
         catch(Exception e){}
        }
       }

     class Solution1{
      public static void main(String a[]){
      System.out.println(Solution.x);
        }
        }

Ora il mio intx statico verrà inizializzato in modo dinamico .. Bcoz quando il compilatore passerà a Solution.x caricherà la classe di soluzione e il caricamento del blocco statico al momento del caricamento della classe ... In modo da poter inizializzare dinamicamente quel membro di dati statici ..

}

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.