Блоки статичної ініціалізації


265

Наскільки я зрозумів, "блок статичної ініціалізації" використовується для встановлення значень статичного поля, якщо це неможливо зробити в одному рядку.

Але я не розумію, для чого нам потрібен спеціальний блок для цього. Наприклад, ми оголошуємо поле статичним (без призначення значення). А потім напишіть кілька рядків коду, які генерують та призначають значення вищевказаному статичному полі.

Навіщо нам потрібні ці рядки в спеціальному блоці типу static {...}:?


6
Незначні відгуки, але це допоможе, якщо ви зможете чітко висловити свої припущення, а отже, уточнити, яка відповідь є правильною. коли я вперше прочитав ваше запитання, я неправильно зрозумів і думав , ви знаєте різницю між {...}проти static {...}. (в такому випадку Джон Скіт однозначно відповів на ваше питання набагато краще)
David T.

1
Це питання дуже незрозуміле; у вас є відповіді, які розбираються і роблять багато довгодушних здогадок про те, що ви мали на увазі. Як щодо того, щоб чітко виписати приклад блоку статичної ініціалізації, який ви маєте на увазі, та вашу альтернативу, щоб люди мали щось зрозуміле, щоб відповісти?
Дон Хетч

Відповіді:


430

Нестатичних блок:

{
    // Do Something...
}

Отримує виклики кожного разу, коли створюється екземпляр класу. Статичний блок тільки викликається один раз , коли сам клас инициализируется, незалежно від того , скільки об'єктів цього типу ви створюєте.

Приклад:

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

Це відбитки:

Static
Non-static block
Non-static block

107
Чому це прийнята відповідь? Це навіть не відповідає на питання.
Пол Беллора

43
Він відповідає на питання: "Це викликається кожного разу, коли клас побудований. Статичний блок викликається лише один раз, незалежно від того, скільки об'єктів цього типу ви створюєте."
Адам Арольд

83
Для допитливого читача нестатичний блок фактично копіюється компілятором Java у кожен конструктор, у якого є клас ( джерело ). Тому завдання конструктора все ще є ініціалізацією полів.
Мартін Андерссон

2
Прийнята відповідь повинна бути такою: stackoverflow.com/a/2420404/363573 . Ця відповідь подає приклад із реального життя, де вам потрібні статичні блоки.
Стефан

16
Чому ця відповідь раптом стає оскарженою? Ви можете не погодитися з тим, що ця відповідь прийнята, але це, безумовно, жодним чином не є помилковим чи оманливим. Просто намагається допомогти зрозуміти ці мовні конструкції простим прикладом.
Фредерік Wordenskjold

132

Якби вони не були в блоці статичної ініціалізації, де вони були б? Як би ви оголосили змінну, яка мала бути локальною лише для ініціалізації, і відрізнити її від поля? Наприклад, як ви хочете написати:

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

Якби firstі secondне були в блоці, вони виглядали б як поля. Якби вони були в блоці без staticнього, це вважатиметься блоком ініціалізації екземпляра замість статичного блоку ініціалізації, тож воно буде виконуватися один раз на кожен побудований екземпляр, а не один раз усього.

Тепер у цьому конкретному випадку ви можете використовувати статичний метод замість цього:

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

... але це не працює, якщо є кілька змінних, які ви хочете призначити в одному блоці, або жодна (наприклад, якщо ви просто хочете щось записати - або, можливо, ініціалізувати рідну бібліотеку).


1
Чи відбувається статичний блок перед призначенням або після статичних змінних? private static int widgets = 0; static{widgets = 2;}
Weishi Zeng

1
Було цікаво, чи статичний блок відбудеться перед призначенням статичних змінних або після них. Наприклад, private static int widgets = 0; static{widgets = 2;}з'ясувалося, що присвоєння '=' відбувається в порядку, що означає, що '=', поставлений перший, буде призначений першим. Наведений вище приклад дасть «віджетам» значення 2. (PS не знав, що коментарі можна редагувати лише за 5 хв ...)
Weishi Zeng

@WeishiZeng: Так, це зафіксовано в docs.oracle.com/javase/specs/jls/se8/html/… - пункт 9.
Джон Скіт

Але чи не могли ви також використовувати приватний статичний метод, який має точно такий же код, як блок статичної ініціалізації та призначити віджети приватному статичному методу?
Захарій Краус

1
@Zachary: Ви маєте на увазі повернення значення та присвоєння результату виклику методу? Якщо так, то так - коли будуть призначаючи рівно однієї змінної в результаті блоку. Відредагую мою відповідь деталями приблизно через 7 годин ...
Джон Скіт

103

Ось приклад:

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

Код у "статичних" розділах (их) буде виконуватися під час завантаження класу до того, як будуть побудовані будь-які екземпляри класу (і до того, як будь-які статичні методи будуть викликані з інших місць). Таким чином ви можете переконатися, що ресурси класу готові до використання.

Можливо також наявність нестатичних блоків ініціалізаторів. Вони діють як розширення до набору методів конструктора, визначених для класу. Вони виглядають так само, як статичні блоки ініціалізатора, за винятком того, що ключове слово "статичний" залишається.


4
У цьому конкретному прикладі іноді подвійний брекет- шаблон піддається «зловживанню» :)
BalusC

Це можна зловживати, але, з іншого боку, він чистить деякі помилки і робить деякі види коду трохи більш "твердими". Я програмую в Ерланге для розваги, і ви зачепитесь за тим, що не потребуєте локальних змінних :-)
Pointy

1
<< Код у розділі "статичний" буде виконуватися під час завантаження класу до того, як будуть побудовані будь-які екземпляри класу (і до того, як будь-які статичні методи будуть викликані з інших місць). Таким чином ви можете переконатися, що ресурси класу готові до використання. >> (Який "Pointy" згаданий вище у відповідь) це дуже важливий момент, який слід зазначити, коли мова йде про статичне виконання блоку.
учень

Чи можемо ми це зробити за допомогою методу InitializingBean в методі afterPropertiesSet?
egemen

48

Це також корисно, коли ви насправді не хочете присвоювати значення нічому, наприклад завантажуючи деякий клас лише один раз під час виконання.

Напр

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

Гей, є ще одна перевага, яку ви можете використовувати для обробки винятків. Уявіть, що getStuff()тут викидається те, Exceptionщо дійсно належить до блоку улов:

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

то staticтут корисний ініціалізатор. Ви можете обробити виняток там.

Інший приклад - робити речі після цього, які неможливо зробити під час призначення:

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

Щоб повернутися до прикладу драйвера JDBC, будь-який порядний драйвер JDBC сам також використовує staticініціалізатор, щоб зареєструватися в DriverManager. Дивіться також цю і цю відповідь.


2
Тут криється небезпечний вуду ... статичні ініціалізатори запускаються методом синтетичного Clinit (), який неявно синхронізований . Це означає, що JVM придбає замок на відповідному файлі класу. Це може призвести до тупикової ситуації в багатопотокових середовищах, якщо два класи намагаються завантажувати один одного, і кожен починає завантажувати в інший потік. Дивіться www-01.ibm.com/support/docview.wss?uid=swg1IV48872
Ajax

@Ajax: Я вважаю, що це помилка або у відповідному драйвері JDBC, або у коді програми, відповідальному за його завантаження. Зазвичай, у випадку пристойних драйверів JDBC, якщо ви завантажуєте його лише один раз у всьому світі під час запуску програми, нічого не має значення.
BalusC

Це, звичайно, буде помилка, але не зовсім виною драйвера JDBC. Можливо, у драйвера невинно є свої статичні ініціалізатори, а може, ви невинно ініціалізуєте цей клас разом з деякими іншими у вашій програмі, і, о, ні, деякі несподівані класи циклічно завантажують один одного, і тепер ваша програма застоюється. Я виявив це завдяки тупику між java.awt.AWTEvent та sun.util.logging.PlatformLogger. Я лише торкнувся AWTEvent, щоб сказати, що він працює без голови, і деякі інші лінди завершилися завантаженням PlatformLogger ... який AWTEvent також завантажує.
Аякс

1
Обидва класи закінчувались синхронізованими на різних потоках, і моя збірка зайшла в тупик приблизно на 1/150 прогонів. Отже, я зараз набагато обережніший при навантаженні класів у статичні блоки. У випадку, про який я згадував вище, використовуючи відкладений шаблон постачальника, де я міг би створити проміжний клас провайдера негайно (без шансу на тупик), ініціалізувати поле, а потім, коли воно фактично має доступ (у не синхронізованому доступі до поля), то я фактично завантажую класи, які можуть спричинити тупик.
Аякс

11

Я б сказав, що static blockце просто синтаксичний цукор. Нічого, що можна було б зробити з staticблоком і ні з чим іншим.

Щоб повторно використовувати приклади, розміщені тут.

Цей фрагмент коду можна переписати, не використовуючи staticініціалізатор.

Метод №1: С static

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

Метод №2: Без 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

Існує кілька фактичних причин того, що потрібно існувати:

  1. ініціалізація static finalчленів, ініціалізація яких може спричинити виняток
  2. ініціалізація static finalчленів з обчисленими значеннями

Люди, як правило, використовують static {}блоки як зручний спосіб ініціалізації речей, від яких залежить клас також під час виконання - наприклад, забезпечення завантаження певного класу (наприклад, драйвери JDBC). Це можна зробити і іншими способами; однак дві речі, про які я згадую вище, можуть бути виконані лише з такою конструкцією, як static {}блок.


8

Ви можете виконувати біти коду один раз для класу, перш ніж об'єкт будується в статичних блоках.

Напр

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

Поширена помилка думати, що статичний блок має доступ лише до статичних полів. Для цього я хотів би показати нижче фрагмент коду, який я досить часто використовую в реальних проектах (частково скопійований з іншої відповіді в дещо іншому контексті):

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

Тут ініціалізатор використовується для підтримки індексу ( ALIAS_MAP), для набору набору псевдонімів до початкового типу enum. Він призначений як розширення до вбудованого методу valueOf, наданого самим Enumсобою.

Як бачите, статичний ініціалізатор отримує доступ навіть до privateполя aliases. Важливо розуміти, що staticблок вже має доступ до Enumекземплярів значень (наприклад ENGLISH). Це тому, що порядок ініціалізації та виконання у випадку Enumтипів , так само, як якщо б static privateполя були ініціалізовані з екземплярами до staticвиклику блоків:

  1. Ці Enumконстанти , які є неявними полями статичних. Для цього потрібні блоки конструктора та екземпляра Enum та ініціалізація екземплярів.
  2. static блокування та ініціалізація статичних полів у порядку виникнення.

staticВажливо відзначити цю ініціалізацію поза замовленням (конструктор перед блоком). Це трапляється і тоді, коли ми ініціалізуємо статичні поля з екземплярами аналогічно Singleton (зроблено спрощення):

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

Ми бачимо такий результат:

Static Block 1
Constructor
Static Block 2
In Main
Constructor

Зрозуміло, що статична ініціалізація насправді може статися перед конструктором і навіть після:

Простий доступ до Foo в основному методі призводить до завантаження класу та запуску статичної ініціалізації. Але в рамках статичної ініціалізації ми знову викликаємо конструктори для статичних полів, після чого він поновлює статичну ініціалізацію, і завершує конструктор, викликаний зсередини основного методу. Досить складна ситуація, для якої я сподіваюся, що при нормальному кодуванні нам не доведеться мати справу.

Більш детальну інформацію про це дивіться у книзі " Ефективна Java ".


1
Доступ до aliasesне означає, що статичний блок може отримати доступ до нестатичних членів. aliasesдоступ має через Languageзначення, повернені методом / static / values(). Як ви згадуєте, той факт, що змінні перерахування вже доступні в цей момент, є незвичним бітом - нестатичні члени регулярних класів не були б доступні в цій ситуації.
Ігнаціо

Статичний блок все ще має доступ лише до статичних полів (у випадку ваших переліків ENGLISH, GERMAN, ...), які в даному випадку є об'єктами. Оскільки статичні поля є самими об'єктами, ви можете отримати доступ до поля екземпляра статичного об’єкта.
Свамі 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; } } Наведений вище код не відрізняється від прикладу enum і все ще дозволяє отримати доступ до змінної екземпляра всередині статичного блоку
Swami PR

@SwamiPR насправді він компілює, на мій подив, і я маю згоду, що код в принципі не відрізняється. Мені потрібно перечитати специфікацію Java, я відчуваю, що я щось пропустив. Хороша відповідь, спасибі
YoYo

@SwamiPR Проблема полягає в тому, що ми повинні використовувати Enum. Це найкращий спосіб гарантувати, що ми вказуємо на особливі екземпляри "- дивіться тут . І до ваших питань, я зробив кілька оновлень.
YoYo

3

Якщо ваші статичні змінні потрібно встановити під час виконання, тоді static {...}блок буде дуже корисним.

Наприклад, якщо вам потрібно встановити статичний член на значення, яке зберігається у конфігураційному файлі чи базі даних.

Також корисно, коли ви хочете додати значення статичному Mapчлену, оскільки ви не можете додати ці значення до початкової декларації члена.


3

Отже, у вас є статичне поле (його також називають "змінною класу", оскільки воно належить до класу, а не до екземпляра класу; іншими словами, воно пов'язане з класом, а не з будь-яким об'єктом), і ви хочете його ініціалізувати. Отже, якщо ви НЕ хочете створювати екземпляр цього класу і хочете маніпулювати цим статичним полем, ви можете це зробити трьома способами:

1- Просто ініціалізуйте його, коли оголошуєте змінну:

static int x = 3;

2- Мають статичний ініціалізаційний блок:

static int x;

static {
 x=3;
}

3- Мати метод класу (статичний метод), який здійснює доступ до змінної класу та ініціалізує її: це альтернатива вищезазначеному статичному блоку; ви можете написати приватний статичний метод:

public static int x=initializeX();

private static int initializeX(){
 return 3;
}

Тепер чому б ви використовували статичний ініціалізаційний блок замість статичних методів?

Це дійсно до того, що потрібно у вашій програмі. Але ви повинні знати, що статичний ініціалізаційний блок викликається один раз, і єдиною перевагою методу класу є те, що вони можуть бути використані пізніше, якщо вам потрібно повторно ініціалізувати змінну класу.

скажімо, у вас є складний масив у вашій програмі. Ви форматувати його (використовуючи для циклу, наприклад) , а потім значення в цьому масиві буде змінюватися в протягом всієї програми , але потім в якийсь - то момент ви хочете реініціалізіровать (повернутися до початкового значення). У цьому випадку ви можете викликати приватний статичний метод. Якщо вам не потрібно у вашій програмі повторно ініціалізувати значення, ви можете просто використовувати статичний блок і не потребувати статичного методу, оскільки ви не збираєтесь його використовувати пізніше в програмі.

Примітка: статичні блоки викликаються в тому порядку, в якому вони відображаються в коді.

Приклад 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
 }

}

Приклад 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

Як доповнення, як сказав @Pointy

Код у "статичних" розділах (их) буде виконуватися під час завантаження класу до того, як будуть побудовані будь-які екземпляри класу (і до того, як будь-які статичні методи будуть викликані з інших місць).

Його слід додати System.loadLibrary("I_am_native_library")до статичного блоку.

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

Це гарантує, що до навантаження відповідної бібліотеки в пам'ять не буде викликано нативного методу.

За даними loadLibrary від oracle :

Якщо цей метод викликається декілька разів з однаковим іменем бібліотеки, другий та наступні дзвінки ігноруються.

Тож зовсім несподівано, якщо System.loadLibrary не використовується, щоб уникнути багаторазового завантаження бібліотеки.


0

Спочатку вам потрібно зрозуміти, що самі ваші класи додатків примірники java.class.Classоб'єктів під час виконання. Це коли запускаються ваші статичні блоки. Отже, ви можете це зробити:

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

}

і він видав би "myInt is 1" для консолі. Зауважте, що я не інстанціював жодного класу.


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

статичний блок використовується для будь-якої технології для ініціалізації статичного члена даних динамічним способом, або ми можемо сказати, для динамічної ініціалізації статичного блоку члена статичних даних використовується статичний блок .. Тому що для нестатичної ініціалізації члена даних у нас є конструктор, але у нас немає будь-яке місце, де ми можемо динамічно ініціалізувати статичний член даних

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

Тепер мій статичний int x буде ініціалізуватися динамічно. Тому, коли компілятор перейде до Solution.x, він завантажить клас рішення та навантаження статичного блоку під час завантаження класу. Отже, ми зможемо динамічно ініціалізувати цей член статичних даних ..

}

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.