Bloki inicjalizacji statycznej

265

O ile rozumiem, „blok inicjalizacji statycznej” służy do ustawiania wartości pola statycznego, jeśli nie można tego zrobić w jednym wierszu.

Ale nie rozumiem, dlaczego potrzebujemy do tego specjalnego bloku. Na przykład deklarujemy pole jako statyczne (bez przypisania wartości). A następnie napisz kilka wierszy kodu, które generują i przypisują wartość do wyżej zadeklarowanego pola statycznego.

Dlaczego potrzebujemy tych linii w specjalnym bloku, takim jak static {...}:?

rzymski
źródło
6
Drobne informacje zwrotne, ale pomogłoby to, gdybyś mógł jasno określić swoje założenia, a tym samym wyjaśnić, która odpowiedź jest prawidłowa. Kiedy po raz pierwszy przeczytać pytanie, błędnie zrozumiane i że wiesz różnicę między {...}vs static {...}. (w takim przypadku Jon Skeet zdecydowanie lepiej odpowiedział na twoje pytanie)
David T.
1
To pytanie jest bardzo niejasne; masz odpowiadających, którzy mieszają się i robią wiele długich podejrzeń o tym, co miałeś na myśli. Co powiesz na wyraźne zapisanie przykładowego bloku inicjalizacji statycznej, który masz na myśli, i swojej alternatywy, aby ludzie mieli coś jasnego do odpowiedzi?
Don Hatch

Odpowiedzi:

430

Blok niestatyczny:

{
    // Do Something...
}

Pobiera wywołanie za każdym razem, gdy tworzona jest instancja klasy. Statyczny blok tylko jest wywoływana raz , kiedy sama klasa jest inicjowany, bez względu na to jak wiele obiektów tego typu utworzyć.

Przykład:

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

To drukuje:

Static
Non-static block
Non-static block
Frederik Wordenskjold
źródło
107
Dlaczego jest to akceptowana odpowiedź? To nawet nie odpowiada na pytanie.
Paul Bellora
43
Odpowiada na pytanie: „Jest wywoływane za każdym razem, gdy budowana jest klasa. Blok statyczny jest wywoływany tylko raz, bez względu na to, ile obiektów tego typu utworzysz”.
Adam Arold
83
Dla ciekawskiego czytelnika blok antystatyczny jest w rzeczywistości kopiowany przez kompilator Java do każdego konstruktora, który ma klasa ( źródło ). Więc nadal zadaniem konstruktora jest inicjowanie pól.
Martin Andersson,
2
Akceptowana odpowiedź powinna brzmieć następująco: stackoverflow.com/a/2420404/363573 . Ta odpowiedź przedstawia prawdziwy przykład życia, w którym potrzebujesz bloków statycznych.
Stephan
16
Dlaczego ta odpowiedź nagle zostaje odrzucona? Możesz nie zgodzić się z tym, że jest to zaakceptowana odpowiedź, ale z pewnością nie jest w żaden sposób zła ani myląca. Po prostu stara się pomóc w zrozumieniu tych konstrukcji językowych za pomocą prostego przykładu.
Frederik Wordenskjold,
132

Gdyby nie byli w statycznym bloku inicjującym, gdzie by byli? Jak zadeklarowałbyś zmienną, która miała być lokalna do celów inicjalizacji, i odróżnić ją od pola? Na przykład, w jaki sposób ty chcesz napisać:

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

Jeśli firsti secondnie były w bloku, oni wyglądają jak pola. Gdyby znajdowali się w bloku bez staticniego, byłby to liczony jako blok inicjalizacji instancji zamiast statycznego bloku inicjalizacji, więc byłby wykonywany raz dla każdej zbudowanej instancji, a nie w sumie.

Teraz w tym konkretnym przypadku możesz zamiast tego użyć metody statycznej:

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

... ale to nie działa, gdy w tym samym bloku jest wiele zmiennych, które chcesz przypisać, lub nie ma ich wcale (np. jeśli chcesz tylko coś zalogować - lub może zainicjować bibliotekę natywną).

Jon Skeet
źródło
1
Czy blok statyczny występuje przed przypisaniem zmiennych statycznych lub po nim? private static int widgets = 0; static{widgets = 2;}
Weishi Zeng
1
Był ciekawy, czy blok statyczny wystąpi przed przypisaniem zmiennych statycznych lub po nim. np. private static int widgets = 0; static{widgets = 2;}ustalono, że przypisanie „=” odbywa się w kolejności, co oznacza, że ​​najpierw wstawione zostanie „=”. Powyższy przykład da „widżetom” wartość 2. (PS nie wiedział, że komentarze można edytować tylko w 5 minut ...)
Weishi Zeng
@WeishiZeng: Tak, jest to udokumentowane w docs.oracle.com/javase/specs/jls/se8/html/… - punkt 9.
Jon Skeet
Ale czy nie można również użyć prywatnej metody statycznej, która ma dokładnie ten sam kod co blok inicjalizacji statycznej i przypisać widżety do prywatnej metody statycznej?
Zachary Kraus,
1
@Zachary: Czy masz na myśli zwracanie wartości i przypisywanie wyniku wywołania metody? Jeśli tak, tak - jeśli przypisanie do dokładnie jednej zmiennej w wyniku bloku. Zmodyfikuję moją odpowiedź ze szczegółami za około 7 godzin ...
Jon Skeet,
103

Oto przykład:

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

Kod w sekcji „statycznej” zostanie wykonany w czasie ładowania klasy, przed zbudowaniem jakichkolwiek instancji klasy (oraz przed wywołaniem jakichkolwiek metod statycznych z innego miejsca). W ten sposób możesz upewnić się, że zasoby klasy są gotowe do użycia.

Możliwe jest także zastosowanie statycznych bloków inicjalizacyjnych. Działają one jak rozszerzenia zestawu metod konstruktora zdefiniowanych dla klasy. Wyglądają jak statyczne bloki inicjujące, z wyjątkiem tego, że słowo kluczowe „statyczny” jest pominięte.

Pointy
źródło
4
W tym konkretnym przykładzie czasami podwójny nawias klamrowy jest „nadużywany” :)
BalusC
Można go nadużywać, ale z drugiej strony porządkuje niektóre bałagany i czyni niektóre rodzaje kodu nieco bardziej „solidnymi”. Programuję w Erlangu dla zabawy, a ty uzależniasz się od niepotrzebnych zmiennych lokalnych :-)
Pointy
1
<< Kod w sekcji „statycznej” zostanie wykonany w czasie ładowania klasy, przed zbudowaniem jakichkolwiek instancji klasy (i przed wywołaniem jakichkolwiek metod statycznych z innego miejsca). W ten sposób możesz upewnić się, że zasoby klasy są gotowe do użycia. >> (Który „Pointy” wspomniany w powyższej odpowiedzi) jest to bardzo ważny punkt, na który należy zwrócić uwagę, jeśli chodzi o wykonanie bloku statycznego.
uczeń
Czy możemy to zrobić za pomocą metody InitializingBean in after afterPropertiesSet?
egemen
48

Jest to również przydatne, gdy w rzeczywistości nie chcesz przypisywać wartości do niczego, na przykład ładując jakąś klasę tylko raz w czasie wykonywania.

Na przykład

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

Hej, jest jeszcze jedna korzyść, możesz jej użyć do obsługi wyjątków. Wyobraź sobie, że getStuff()tutaj rzuca coś, Exceptionco naprawdę należy do bloku catch:

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

wtedy staticprzydaje się inicjator. Możesz obsłużyć wyjątek tam.

Innym przykładem jest robienie rzeczy, których nie można zrobić podczas przypisywania:

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

Aby wrócić do przykładu sterownika JDBC, każdy przyzwoity sterownik JDBC również korzysta z staticinicjatora, aby zarejestrować się w DriverManager. Zobacz także i odpowiedź.

BalusC
źródło
2
Na tym polega niebezpieczne voodoo ... statyczne inicjalizatory są uruchamiane w syntetycznej metodzie klinit (), która jest domyślnie synchronizowana . Oznacza to, że JVM uzyska blokadę na danym pliku klasy. Może to prowadzić do impasu w środowiskach wielowątkowych, jeśli dwie klasy spróbują się załadować, a każda zacznie ładować w innym wątku. Zobacz www-01.ibm.com/support/docview.wss?uid=swg1IV48872
Ajax
@Ajax: Uważam to za błąd w danym sterowniku JDBC lub w kodzie aplikacji odpowiedzialnym za ładowanie go. Zwykle w przypadku porządnych sterowników JDBC, o ile ładujesz go tylko raz w całej aplikacji podczas uruchamiania aplikacji, nie ma to znaczenia.
BalusC
Z pewnością byłby to jednak błąd, ale nie do końca wina sterownika JDBC. Być może sterownik niewinnie ma własne inicjalizatory statyczne, a może niewinnie inicjujesz tę klasę wraz z kilkoma innymi w swojej aplikacji, i, nie, niektóre nieoczekiwane klasy ładują się cyklicznie, a teraz twoja aplikacja się blokuje. Odkryłem to dzięki impasowi między java.awt.AWTEvent a sun.util.logging.PlatformLogger. Dotknąłem tylko AWTEvent, żeby powiedzieć mu, żeby działał bezgłowo, a kilka innych bibliotek kończy ładowanie PlatformLogger ... które AWTEvent również ładuje.
Ajax,
1
Obie klasy zostały zsynchronizowane na różnych wątkach, a moja kompilacja zablokowała około 1/150 przebiegów. Tak więc jestem teraz o wiele bardziej ostrożny przy ładowaniu klas w blokach statycznych. W przypadku, o którym wspomniałem powyżej, używając wzorca odroczonego dostawcy, w którym mógłbym natychmiast utworzyć tymczasową klasę dostawcy (bez szansy na zakleszczenie), zainicjować pole, a następnie, kiedy jest ono faktycznie dostępne (w przypadku niezsynchronizowanego dostępu do pola), następnie ładuję klasy, które mogą powodować impas.
Ajax,
11

Powiedziałbym, że static blockto tylko cukier syntaktyczny. Nic nie można zrobić z staticblokiem, a nie z niczym innym.

Aby ponownie użyć niektórych przykładów opublikowanych tutaj.

Ten fragment kodu można ponownie napisać bez użycia staticinicjatora.

Metoda nr 1: Z static

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

Metoda nr 2: Bez 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;
}
użytkownik1508893
źródło
10

Istnieje kilka faktycznych powodów, dla których musi istnieć:

  1. inicjowanie static finalczłonków, których inicjalizacja może zgłosić wyjątek
  2. inicjowanie static finalelementów z wartościami obliczonymi

Ludzie zwykle używają static {}bloków jako wygodnego sposobu inicjowania rzeczy, od których klasa zależy również w środowisku wykonawczym - takich jak ładowanie określonej klasy (np. Sterowniki JDBC). Można to zrobić na inne sposoby; jednak dwie rzeczy, o których wspomniałem powyżej, można wykonać tylko za pomocą konstrukcji takiej jak static {}blok.

D.Shawley
źródło
8

Możesz wykonać bity kodu raz dla klasy, zanim obiekt zostanie skonstruowany w blokach statycznych.

Na przykład

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());
  }
}
Pierre-Antoine LaFayette
źródło
7

Powszechnym błędem jest myślenie, że blok statyczny ma dostęp tylko do pól statycznych. W tym celu chciałbym pokazać poniżej fragment kodu, który dość często używam w rzeczywistych projektach (skopiowany częściowo z innej odpowiedzi w nieco innym kontekście):

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

W tym przypadku inicjalizator służy do utrzymywania indeksu ( ALIAS_MAP) w celu odwzorowania zestawu aliasów z powrotem na oryginalny typ wyliczenia. Ma to stanowić rozszerzenie wbudowanej metody valueOf dostarczonej przez Enumsiebie.

Jak widać, statyczny inicjalizator ma dostęp nawet do privatepola aliases. Ważne jest, aby zrozumieć, że staticblok ma już dostęp do Enuminstancji wartości (np ENGLISH.). Dzieje się tak, ponieważ kolejność inicjowania i wykonywania w przypadku Enumtypów , tak jakby static privatepola zostały zainicjowane instancjami przed staticwywołaniem bloków:

  1. Na Enumstałe, które są ukryte pola statycznego. Wymaga to konstruktora Enum i bloków instancji, a także inicjalizacji instancji w pierwszej kolejności.
  2. static blok i inicjalizacja pól statycznych w kolejności występowania.

staticNależy pamiętać o tej inicjalizacji poza kolejnością (konstruktor przed blokiem). Dzieje się tak również, gdy inicjalizujemy pola statyczne instancjami podobnie jak Singleton (dokonane uproszczenia):

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

Widzimy następujące dane wyjściowe:

Static Block 1
Constructor
Static Block 2
In Main
Constructor

Oczywiste jest, że inicjalizacja statyczna może faktycznie nastąpić przed konstruktorem, a nawet po:

Prosty dostęp do Foo w głównej metodzie powoduje załadowanie klasy i rozpoczęcie inicjalizacji statycznej. Ale w ramach inicjalizacji statycznej ponownie wywołujemy konstruktory dla pól statycznych, po czym wznawia się inicjalizację statyczną i kończy konstruktor wywoływany z metody głównej. Raczej złożona sytuacja, w przypadku której mam nadzieję, że przy normalnym kodowaniu nie musielibyśmy się uporać.

Więcej informacji na ten temat można znaleźć w książce „ Skuteczna Java ”.

Racja
źródło
1
Posiadanie dostępu do aliasesnie oznacza, że ​​blok statyczny może uzyskać dostęp do elementów niestatycznych. aliasesjest dostępny poprzez Languagewartości zwracane przez values()metodę / static / . Jak wspominasz, fakt, że zmienne wyliczeniowe są już w tym momencie dostępne, jest niezwykłym bitem - niestatyczne elementy klas regularnych nie byłyby dostępne w tej sytuacji.
Ignazio
Blok statyczny nadal ma dostęp tylko do pól statycznych (w przypadku enum ENGLISH, GERMAN, ...), które w tym przypadku są obiektami. Ponieważ pola statyczne same w sobie są obiektami, można uzyskać dostęp do pola instancji obiektu statycznego.
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; } } Powyższy kod nie różni się od przykładu z wyliczeniem i nadal umożliwia dostęp do zmiennej instancji wewnątrz bloku statycznego
Swami PR
@SwamiPR rzeczywiście się kompiluje, ku mojemu zaskoczeniu, i muszę zgodzić się, że kod w zasadzie nie różni się. Muszę ponownie przeczytać specyfikację Java, czuję, że coś mi umknęło. Dobra odpowiedź z powrotem, dzięki.
YoYo
@SwamiPR Naprawdę chodzi o to, że powinniśmy używać Enum. Jest to najlepszy sposób, aby zagwarantować, że wskazujemy pojedyncze przypadki ”- patrz tutaj . I do twoich punktów, dokonałem kilku aktualizacji.
YoYo
3

Jeśli zmienne statyczne wymagają ustawienia w czasie wykonywania, static {...}blok jest bardzo pomocny.

Na przykład, jeśli chcesz ustawić element statyczny na wartość przechowywaną w pliku konfiguracyjnym lub bazie danych.

Przydatne również, gdy chcesz dodać wartości do elementu statycznego, Mapponieważ nie możesz dodać tych wartości w początkowej deklaracji elementu.

Marcus Leon
źródło
3

Masz więc pole statyczne (nazywane jest ono również „zmienną klasy”, ponieważ należy do klasy, a nie do instancji klasy; innymi słowy, jest powiązane z klasą, a nie z jakimkolwiek obiektem) i chcesz ją zainicjować. Jeśli więc NIE chcesz utworzyć wystąpienia tej klasy i chcesz manipulować tym polem statycznym, możesz to zrobić na trzy sposoby:

1- Po prostu zainicjuj go, kiedy deklarujesz zmienną:

static int x = 3;

2- Mają statyczny blok inicjujący:

static int x;

static {
 x=3;
}

3- Mieć metodę klasy (metoda statyczna), która uzyskuje dostęp do zmiennej klasy i inicjuje ją: jest to alternatywa dla powyższego bloku statycznego; możesz napisać prywatną metodę statyczną:

public static int x=initializeX();

private static int initializeX(){
 return 3;
}

Dlaczego więc miałbyś używać statycznego bloku inicjującego zamiast statycznych metod?

To naprawdę zależy od tego, czego potrzebujesz w swoim programie. Ale musisz wiedzieć, że statyczny blok inicjujący jest wywoływany raz, a jedyną zaletą metody klasowej jest to, że można ją ponownie wykorzystać później, jeśli zajdzie potrzeba ponownego zainicjowania zmiennej klasy.

powiedzmy, że masz złożony program w swoim programie. Inicjujesz go ( na przykład za pomocą pętli for), a następnie wartości w tej tablicy zmienią się w całym programie, ale w pewnym momencie chcesz go ponownie zainicjować (wróć do wartości początkowej). W takim przypadku możesz wywołać prywatną metodę statyczną. Jeśli nie musisz ponownie inicjować wartości w swoim programie, możesz po prostu użyć bloku statycznego i nie potrzebujesz metody statycznej, ponieważ nie będziesz jej później używać w programie.

Uwaga: bloki statyczne są wywoływane w kolejności, w jakiej występują w kodzie.

Przykład 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
 }

}

Przykład 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
 }

}
Randa Sbeity
źródło
0

Jako uzupełnienie, jak powiedział @Pointy

Kod w sekcji „statycznej” zostanie wykonany w czasie ładowania klasy, przed zbudowaniem jakichkolwiek instancji klasy (oraz przed wywołaniem jakichkolwiek metod statycznych z innego miejsca).

Ma się dodać System.loadLibrary("I_am_native_library")do bloku statycznego.

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

Zagwarantuje to, że żadna natywna metoda nie zostanie wywołana przed załadowaniem powiązanej biblioteki do pamięci.

Według loadLibrary z Oracle :

Jeśli ta metoda zostanie wywołana więcej niż raz z tą samą nazwą biblioteki, drugie i kolejne wywołania są ignorowane.

Zupełnie więc nieoczekiwanie, umieszczenie System.loadLibrary nie jest używane, aby uniknąć wielokrotnego ładowania biblioteki.

Eugene
źródło
0

Najpierw musisz zrozumieć, że same klasy aplikacji są tworzone do java.class.Classobiektów podczas działania. To wtedy uruchamiane są bloki statyczne. Więc możesz to zrobić:

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

}

i wypisałby na konsoli „myInt is 1”. Pamiętaj, że nie utworzyłem żadnej klasy.

Emmanuel Osimosu
źródło
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");
    } 
}
Vid
źródło
-1

blok statyczny jest wykorzystywany w każdej technologii do dynamicznego inicjowania elementu danych statycznych, lub możemy powiedzieć, że do dynamicznej inicjalizacji elementu danych statycznych używany jest blok statyczny. Ponieważ do inicjacji elementów statycznych nie mamy, mamy konstruktora, ale nie mamy dowolne miejsce, w którym możemy dynamicznie inicjować element danych statycznych

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

Teraz moja statyczna int x zainicjuje się dynamicznie .. Boz, gdy kompilator przejdzie do Solution.x, załaduje klasę rozwiązania i ładowanie bloku statycznego w czasie ładowania klasy .. Więc możemy dynamicznie zainicjalizować tego członka danych statycznych ..

}

Bieg
źródło