Blok Inisialisasi Statis

265

Sejauh yang saya mengerti "blok inisialisasi statis" digunakan untuk mengatur nilai-nilai bidang statis jika tidak dapat dilakukan dalam satu baris.

Tapi saya tidak mengerti mengapa kita perlu blok khusus untuk itu. Sebagai contoh, kami menyatakan bidang sebagai statis (tanpa penugasan nilai). Dan kemudian menulis beberapa baris kode yang menghasilkan dan menetapkan nilai ke bidang statis yang dinyatakan di atas.

Mengapa kita perlu garis ini di blok khusus seperti: static {...}?

Roma
sumber
6
Umpan balik kecil, tetapi akan membantu jika Anda bisa menyatakan asumsi Anda dengan jelas, dan karenanya menjelaskan jawaban mana yang benar. Ketika saya pertama kali membaca pertanyaan Anda, saya salah mengerti dan berpikir Anda tahu perbedaan antara {...}vs static {...}. (dalam hal ini Jon Skeet pasti menjawab pertanyaan Anda dengan lebih baik)
David T.
1
Pertanyaan ini sangat tidak jelas; Anda memiliki penjawab yang berebut dan membuat banyak dugaan panjang lebar tentang apa yang Anda maksudkan. Bagaimana kalau secara eksplisit menuliskan contoh blok inisialisasi statis yang ada dalam pikiran Anda dan alternatif Anda, sehingga orang memiliki sesuatu yang jelas untuk dijawab?
Don Hatch

Jawaban:

430

The blok non-statis:

{
    // Do Something...
}

Mendapat panggilan setiap kali instance kelas dibangun. The blok statis hanya dipanggil sekali , ketika kelas itu sendiri diinisialisasi, tidak peduli berapa banyak objek dari jenis yang Anda buat.

Contoh:

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

Ini mencetak:

Static
Non-static block
Non-static block
Frederik Wordenskjold
sumber
107
Mengapa ini jawaban yang diterima? Bahkan tidak menjawab pertanyaan.
Paul Bellora
43
Itu menjawab pertanyaan: "Ini dipanggil setiap kali kelas dibangun. Blok statis hanya dipanggil sekali, tidak peduli berapa banyak objek jenis yang Anda buat."
Adam Arold
83
Untuk pembaca yang ingin tahu, blok non-statis sebenarnya disalin oleh Java compiler ke setiap konstruktor yang dimiliki kelas ( sumber ). Jadi masih tugas konstruktor untuk menginisialisasi bidang.
Martin Andersson
2
Jawaban yang diterima harus yang ini: stackoverflow.com/a/2420404/363573 . Jawaban ini menyajikan contoh kehidupan nyata di mana Anda memerlukan blok statis.
Stephan
16
Mengapa jawaban ini tiba-tiba dipungut suara? Anda mungkin tidak setuju tentang ini sebagai jawaban yang diterima, tetapi tentu saja tidak salah atau menyesatkan. Ini hanya mencoba membantu pemahaman konstruksi bahasa ini dengan contoh sederhana.
Frederik Wordenskjold
133

Jika mereka tidak berada di blok inisialisasi statis, di manakah mereka? Bagaimana Anda mendeklarasikan variabel yang hanya dimaksudkan lokal untuk keperluan inisialisasi, dan membedakannya dari bidang? Misalnya, bagaimana Anda ingin menulis:

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

Jika firstdan secondtidak di blok, mereka akan terlihat seperti bidang. Jika mereka berada di blok tanpa staticdi depannya, itu akan dihitung sebagai contoh inisialisasi blok bukan blok inisialisasi statis, sehingga akan dieksekusi sekali per instance yang dibangun daripada total sekali.

Sekarang dalam kasus khusus ini, Anda bisa menggunakan metode statis sebagai gantinya:

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

... tetapi itu tidak berfungsi ketika ada beberapa variabel yang ingin Anda tetapkan dalam blok yang sama, atau tidak ada (misalnya jika Anda hanya ingin mencatat sesuatu - atau mungkin menginisialisasi perpustakaan asli).

Jon Skeet
sumber
1
Apakah blok statis terjadi sebelum variabel statis diberikan atau setelah? private static int widgets = 0; static{widgets = 2;}
Weishi Zeng
1
Ingin tahu apakah blok statis terjadi sebelum variabel statis ditugaskan atau setelah. mis. private static int widgets = 0; static{widgets = 2;}Mengetahui bahwa penugasan '=' terjadi secara berurutan, yang berarti penandaan '=' akan ditetapkan terlebih dahulu. Contoh di atas akan memberikan 'widget' nilai 2. (PS tidak tahu bahwa komentar hanya dapat diedit dalam 5 menit ...)
Weishi Zeng
@WeishiZeng: Ya, ini seperti yang didokumentasikan dalam docs.oracle.com/javase/specs/jls/se8/html/… - poin 9.
Jon Skeet
Tetapi tidak bisakah Anda juga menggunakan metode statis privat yang memiliki kode yang sama persis dengan blok inisialisasi statis dan menetapkan widget ke metode statis privat?
Zachary Kraus
1
@ Zakary: Maksud Anda mengembalikan nilai, dan menetapkan hasil pemanggilan metode? Jika begitu, ya - ketika Anda sedang menugaskan tepat satu variabel sebagai hasil dari blok. Akan mengedit jawaban saya dengan perincian dalam sekitar 7 jam ...
Jon Skeet
103

Ini sebuah contoh:

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

Kode di bagian "statis" akan dieksekusi pada waktu pemuatan kelas, sebelum instance kelas dibuat (dan sebelum metode statis apa pun dipanggil dari tempat lain). Dengan begitu Anda dapat memastikan bahwa semua sumber daya kelas siap digunakan.

Juga dimungkinkan untuk memiliki blok penginisialisasi non-statis. Mereka bertindak seperti ekstensi ke set metode konstruktor yang ditentukan untuk kelas. Mereka terlihat seperti blok penginisialisasi statis, kecuali kata kunci "statis" tidak aktif.

Runcing
sumber
4
Untuk contoh khusus itu kadang-kadang pola penjepit ganda telah "disalahgunakan" :)
BalusC
Ini dapat disalahgunakan, tetapi di sisi lain itu memang membersihkan beberapa kekacauan, dan membuat beberapa jenis kode sedikit lebih "solid." Saya memprogram di Erlang untuk bersenang-senang, dan Anda ketagihan tidak membutuhkan variabel lokal :-)
Pointy
1
<< Kode di bagian "statis" akan dieksekusi pada waktu pemuatan kelas, sebelum setiap instance kelas dikonstruksi (dan sebelum metode statis apa pun dipanggil dari tempat lain). Dengan begitu Anda dapat memastikan bahwa semua sumber daya kelas siap digunakan. >> (Yang "Runcing" yang disebutkan dalam jawaban di atas) ini adalah poin yang sangat penting untuk dicatat ketika datang ke eksekusi blok statis.
pelajar
Bisakah kita melakukan ini dengan menggunakan InitializingBean setelah metode afterPropertiesSet?
egemen
48

Ini juga berguna ketika Anda sebenarnya tidak ingin memberikan nilai pada apa pun, seperti memuat beberapa kelas hanya sekali selama runtime.

Misalnya

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

Hei, ada manfaat lain, Anda bisa menggunakannya untuk menangani pengecualian. Bayangkan di getStuff()sini melempar Exceptionyang benar-benar termasuk dalam blok penangkap:

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

maka staticinisialisasi berguna di sini. Anda dapat menangani pengecualian di sana.

Contoh lain adalah melakukan hal-hal setelah itu yang tidak dapat dilakukan selama menugaskan:

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

Untuk kembali ke contoh driver JDBC, setiap driver JDBC yang layak itu sendiri juga menggunakan staticpenginisialisasi untuk mendaftarkan dirinya di DriverManager. Juga lihat ini dan ini jawabannya.

BalusC
sumber
2
Di sinilah letak voodoo berbahaya ... inisialisasi statis dijalankan dalam metode Clinit Sintetis (), yang secara tersinkronisasi disinkronkan . Ini berarti bahwa JVM akan mendapatkan kunci pada classfile yang bersangkutan. Ini dapat menyebabkan kebuntuan di lingkungan multithreaded jika dua kelas mencoba memuat satu sama lain, dan masing-masing mulai memuat di utas yang berbeda. Lihat www-01.ibm.com/support/docview.wss?uid=swg1IV48872
Ajax
@Ajax: Saya menganggap ini sebagai bug pada driver JDBC yang dimaksud, atau dalam kode aplikasi yang bertanggung jawab untuk memuatnya. Biasanya, dalam hal driver JDBC yang layak, selama Anda memuatnya hanya sekali di seluruh aplikasi selama startup aplikasi, tidak ada masalah.
BalusC
Namun hal itu tentu saja merupakan bug tetapi bukan sepenuhnya kesalahan driver JDBC. Mungkin pengemudi dengan polosnya memiliki inisialisasi statisnya sendiri, dan mungkin Anda dengan polosnya menginisialisasi kelas ini bersama dengan beberapa yang lain di aplikasi Anda, dan, oh tidak, beberapa kelas tak terduga secara siklikal saling memuat satu sama lain, dan sekarang kebuntuan aplikasi Anda. Saya menemukan ini berkat kebuntuan antara java.awt.AWTEvent dan sun.util.logging.PlatformLogger. Saya hanya menyentuh AWTEvent untuk memerintahkannya berjalan tanpa kepala, dan beberapa lib lainnya memuat PlatformLogger ... yang juga dimuat AWTEvent.
Ajax
1
Kedua kelas akhirnya disinkronkan pada utas yang berbeda, dan build saya menemui jalan buntu sekitar 1/150 run. Jadi, saya sekarang lebih berhati-hati dalam pemuatan kelas dalam blok statis. Dalam kasus yang saya sebutkan di atas, menggunakan pola penyedia ditangguhkan di mana saya bisa membuat kelas penyedia sementara segera (tanpa kemungkinan kebuntuan), menginisialisasi bidang, dan kemudian ketika itu benar-benar diakses (dalam akses bidang non-sinkronisasi), maka saya benar-benar memuat kelas yang dapat menyebabkan kebuntuan.
Ajax
11

Saya akan mengatakan static blockini hanya gula sintaksis. Tidak ada yang bisa Anda lakukan dengan staticblok dan tidak dengan apa pun.

Untuk menggunakan kembali beberapa contoh yang diposting di sini.

Potongan kode ini dapat ditulis ulang tanpa menggunakan staticpenginisialisasi.

Metode # 1: Dengan static

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

Metode # 2: Tanpa 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;
}
pengguna1508893
sumber
10

Ada beberapa alasan aktual mengapa diperlukan:

  1. menginisialisasi static finalanggota yang inisialisasi mungkin melemparkan pengecualian
  2. menginisialisasi static finalanggota dengan nilai yang dihitung

Orang-orang cenderung menggunakan static {}blok sebagai cara yang nyaman untuk menginisialisasi hal-hal yang bergantung pada kelas dalam runtime juga - seperti memastikan bahwa kelas tertentu dimuat (misalnya, driver JDBC). Itu bisa dilakukan dengan cara lain; Namun, dua hal yang saya sebutkan di atas hanya dapat dilakukan dengan konstruksi seperti static {}blok.

D.Shawley
sumber
8

Anda bisa mengeksekusi bit kode sekali untuk kelas sebelum objek dibangun di blok statis.

Misalnya

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
sumber
7

Adalah kesalahpahaman umum untuk berpikir bahwa blok statis hanya memiliki akses ke bidang statis. Untuk ini saya ingin menunjukkan potongan kode di bawah ini yang sering saya gunakan dalam proyek kehidupan nyata (disalin sebagian dari jawaban lain dalam konteks yang sedikit berbeda):

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

Di sini penginisialisasi digunakan untuk mempertahankan indeks ( ALIAS_MAP), untuk memetakan set alias kembali ke tipe enum asli. Ini dimaksudkan sebagai perluasan ke metode valueOf bawaan yang disediakan oleh Enumdirinya sendiri.

Seperti yang Anda lihat, penginisialisasi statis mengakses bahkan privatebidang aliases. Penting untuk memahami bahwa staticblok sudah memiliki akses ke Enuminstance nilai (misalnya ENGLISH). Ini karena urutan inisialisasi dan eksekusi dalam kasus Enumtipe , seperti jika static privatebidang telah diinisialisasi dengan instance sebelum staticblok dipanggil:

  1. The Enumkonstanta yang bidang statis implisit. Ini memerlukan konstruktor Enum dan blok instance, dan inisialisasi instance juga terjadi terlebih dahulu.
  2. static blok dan inisialisasi bidang statis dalam urutan kejadian.

Inisialisasi rusak ini (konstruktor sebelum staticblok) penting untuk dicatat. Ini juga terjadi ketika kita menginisialisasi bidang statis dengan instance yang mirip dengan Singleton (penyederhanaan dibuat):

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

Apa yang kita lihat adalah output berikut:

Static Block 1
Constructor
Static Block 2
In Main
Constructor

Yang jelas adalah bahwa inisialisasi statis sebenarnya dapat terjadi sebelum konstruktor, dan bahkan setelah:

Cukup mengakses Foo dalam metode utama, menyebabkan kelas dimuat dan inisialisasi statis dimulai. Tetapi sebagai bagian dari inisialisasi Statis kita kembali memanggil konstruktor untuk bidang statis, setelah itu melanjutkan inisialisasi statis, dan melengkapi konstruktor yang dipanggil dari dalam metode utama. Situasi yang agak rumit yang saya harap dalam coding normal kita tidak harus berurusan dengan.

Untuk info lebih lanjut tentang ini, lihat buku " Java Efektif ".

YoYo
sumber
1
Memiliki akses ke aliasesbukan berarti blok statis dapat mengakses anggota yang tidak statis. aliasesdiakses melalui Languagenilai yang dikembalikan oleh metode / static / values(). Seperti yang Anda sebutkan, fakta bahwa variabel enum sudah tersedia pada saat itu adalah bit yang tidak biasa - anggota non-statis dari kelas reguler tidak akan dapat diakses dalam situasi ini.
Ignazio
Blok statis masih hanya mengakses bidang statis (dalam hal enum Anda BAHASA INGGRIS, JERMAN, ...) yang dalam hal ini adalah objek. Karena bidang statis adalah objek itu sendiri, Anda dapat mengakses bidang contoh objek statis.
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; } } Kode di atas tidak berbeda dari contoh enum dan masih memungkinkan akses variabel instan di dalam blok statis
Swami PR
@ SwamiPR memang mengkompilasi, mengejutkan saya, dan saya harus setuju bahwa kode ini pada prinsipnya tidak berbeda. Saya harus membaca ulang spec Java, saya merasa ada sesuatu yang saya lewatkan. Respon yang baik kembali, terima kasih.
YoYo
@ SwamiPR Masalahnya sebenarnya adalah bahwa kita harus menggunakan Enum. Ini adalah cara terbaik untuk menjamin bahwa kami menunjuk ke instance tunggal - lihat di sini . Dan untuk poin Anda, saya telah membuat beberapa pembaruan.
YoYo
3

Jika variabel statis Anda perlu diset pada saat runtime maka sebuah static {...}blok sangat membantu.

Misalnya, jika Anda perlu mengatur anggota statis ke nilai yang disimpan dalam file konfigurasi atau database.

Juga berguna ketika Anda ingin menambahkan nilai ke anggota statis Mapkarena Anda tidak dapat menambahkan nilai-nilai ini dalam pernyataan anggota awal.

Marcus Leon
sumber
3

Jadi Anda memiliki bidang statis (ini juga disebut "variabel kelas" karena itu milik kelas dan bukan turunan dari kelas; dengan kata lain itu terkait dengan kelas daripada dengan objek apa pun) dan Anda ingin menginisialisasi itu. Jadi jika Anda TIDAK ingin membuat turunan dari kelas ini dan Anda ingin memanipulasi bidang statis ini, Anda bisa melakukannya dengan tiga cara:

1- Cukup inisialisasi ketika Anda mendeklarasikan variabel:

static int x = 3;

2- Memiliki blok inisialisasi statis:

static int x;

static {
 x=3;
}

3 - Memiliki metode kelas (metode statis) yang mengakses variabel kelas dan menginisialisasi itu: ini adalah alternatif untuk blok statis di atas; Anda dapat menulis metode statis pribadi:

public static int x=initializeX();

private static int initializeX(){
 return 3;
}

Sekarang mengapa Anda menggunakan blok inisialisasi statis daripada metode statis?

Terserah apa yang Anda butuhkan dalam program Anda. Tetapi Anda harus tahu bahwa blok inisialisasi statis dipanggil sekali dan satu-satunya keuntungan dari metode kelas adalah bahwa mereka dapat digunakan kembali nanti jika Anda perlu menginisialisasi ulang variabel kelas.

katakanlah Anda memiliki array yang kompleks dalam program Anda. Anda menginisialisasi (menggunakan untuk loop misalnya) dan kemudian nilai-nilai dalam array ini akan berubah sepanjang program tetapi kemudian pada beberapa titik Anda ingin menginisialisasi ulang itu (kembali ke nilai awal). Dalam hal ini Anda dapat memanggil metode statis pribadi. Jika Anda tidak perlu dalam program Anda untuk menginisialisasi ulang nilai-nilai, Anda bisa menggunakan blok statis dan tidak perlu untuk metode statis karena Anda tidak akan menggunakannya nanti dalam program.

Catatan: blok statis dipanggil dalam urutan yang muncul dalam kode.

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

}

Contoh 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
sumber
0

Sebagai pelengkap, seperti kata @Pointy

Kode di bagian "statis" akan dieksekusi pada waktu pemuatan kelas, sebelum instance kelas dibuat (dan sebelum metode statis apa pun dipanggil dari tempat lain).

Seharusnya menambahkan System.loadLibrary("I_am_native_library")ke blok statis.

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

Ini akan menjamin tidak ada metode asli dipanggil sebelum perpustakaan terkait dimuat ke dalam memori.

Menurut loadLibrary dari oracle :

Jika metode ini dipanggil lebih dari satu kali dengan nama pustaka yang sama, panggilan kedua dan selanjutnya diabaikan.

Jadi cukup tidak terduga, menempatkan System.loadLibrary tidak digunakan untuk menghindari perpustakaan dimuat berkali-kali.

Eugene
sumber
0

Pertama-tama Anda perlu memahami bahwa kelas aplikasi Anda sendiri dipakai untuk java.class.Classobjek selama runtime. Ini adalah ketika blok statis Anda dijalankan. Jadi Anda benar-benar dapat melakukan ini:

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

}

dan itu akan mencetak "myInt is 1" ke konsol. Perhatikan bahwa saya belum membuat instance kelas apa pun.

Emmanuel Osimosu
sumber
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
sumber
-1

blok statis digunakan untuk teknologi apa pun untuk menginisialisasi anggota data statis dengan cara yang dinamis, atau kita dapat mengatakan untuk inisialisasi dinamis dari anggota data statis blok statis sedang digunakan .. Karena untuk inisialisasi anggota data non statis kita memiliki konstruktor tetapi kita tidak memiliki tempat di mana kita dapat secara dinamis menginisialisasi anggota data statis

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

Sekarang int x statis saya akan menginisialisasi secara dinamis .. BCOZ ketika kompiler akan pergi ke Solution.x itu akan memuat Kelas Solusi dan beban blok statis pada waktu pemuatan kelas .. Jadi kita dapat secara dinamis menginisialisasi anggota data statis itu ..

}

Arun
sumber