Статические блоки инициализации

265

Насколько я понял, «статический блок инициализации» используется для установки значений статического поля, если это невозможно сделать в одной строке.

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

Зачем нам нужны эти строки в специальном блоке вроде static {...}:?

Римский
источник
6
Небольшая обратная связь, но было бы полезно, если бы вы могли четко изложить свои предположения и, следовательно, уточнить, какой ответ является правильным. когда я впервые прочитал ваш вопрос, я неправильно понял и подумал, что вы знаете разницу между {...}против static {...}. (в этом случае Джон Скит определенно ответил на ваш вопрос лучше)
Дэвид Т.
1
Этот вопрос очень неясен; у вас есть ответчики, карабкающиеся и делающие много скучных догадок о том, что вы имели в виду. Как насчет явной записи примерного статического блока инициализации, который вы имеете в виду, и вашей альтернативы, чтобы у людей было что-то ясное для ответа?
Дон Хэтч

Ответы:

431

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

{
    // 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
Почему этот ответ внезапно получает отрицание? Вы можете не согласиться с тем, что этот ответ является принятым, но он ни в коем случае не является неправильным или вводящим в заблуждение. Это просто попытка помочь пониманию этих языковых конструкций на простом примере.
Фредерик Ворденшельд
133

Если бы они не были в статическом блоке инициализации, где бы они были? Как бы вы объявили переменную, которая должна была быть только локальной для целей инициализации, и отличали бы ее от поля? Например, как бы вы хотели написать:

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

Код в «статических» разделах будет выполняться во время загрузки класса до того, как будут созданы какие-либо экземпляры класса (и до того, как какие-либо статические методы будут вызваны из других источников). Таким образом, вы можете быть уверены, что все ресурсы класса готовы к использованию.

Также возможно иметь нестатические блоки инициализатора. Они действуют как расширения для набора методов конструктора, определенных для класса. Они выглядят так же, как статические блоки инициализатора, за исключением того, что ключевое слово «static» не указано.

Заостренный
источник
4
В этом конкретном примере иногда используется шаблон двойных скобок :)
BalusC
Им можно злоупотреблять, но, с другой стороны, он очищает некоторые беспорядки и делает некоторые виды кода немного более «твердыми». Я программирую на Erlang для удовольствия, и вы подсели на ненужные локальные переменные :-)
Pointy
1
<< Код в «статических» разделах будет выполняться во время загрузки класса, до того, как будут созданы какие-либо экземпляры класса (и до того, как какие-либо статические методы будут вызваны откуда-то еще). Таким образом, вы можете быть уверены, что все ресурсы класса готовы к использованию. >> (Какая «заостренность» упоминается в ответе выше), это очень важный момент, который нужно отметить, когда речь идет о выполнении статического блока.
ученик
Можем ли мы сделать это с помощью InitializingBean после метода afterPropertiesSet?
Эгемен
48

Это также полезно, когда вы на самом деле не хотите присваивать значение чему-либо, например, загружать некоторый класс только один раз во время выполнения.

Например

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

Эй, есть еще одно преимущество, вы можете использовать его для обработки исключений. Представьте, что getStuff()здесь выдается объект, Exceptionкоторый действительно принадлежит блоку catch:

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. Также посмотрите этот и этот ответ.

BalusC
источник
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;
}
user1508893
источник
10

Есть несколько фактических причин, по которым он необходим:

  1. инициализирующие static finalчлены, инициализация которых может вызвать исключение
  2. инициализация static finalчленов с вычисленными значениями

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

D.Shawley
источник
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); 
  } 
} 

Здесь инициализатор используется для поддержки index ( ALIAS_MAP), чтобы отобразить набор псевдонимов обратно в исходный тип перечисления. Он предназначен как расширение встроенного метода 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 в методе main приводит к загрузке класса и запуску статической инициализации. Но как часть статической инициализации мы снова вызываем конструкторы для статических полей, после чего она возобновляет статическую инициализацию и завершает конструктор, вызываемый из основного метода. Довольно сложная ситуация, для которой я надеюсь, что при нормальном кодировании нам не пришлось бы иметь дело с.

Подробнее об этом см. Книгу « Эффективная Java ».

Йо Йо
источник
1
Доступ к aliasesне означает, что статический блок может получить доступ к нестатическим членам. aliasesДоступ осуществляется через Languageзначения, возвращаемые методом / static / values(). Как вы упомянули, тот факт, что переменные перечисления уже доступны в тот момент, является необычным битом - нестатические члены обычных классов не будут доступны в этой ситуации.
Игнацио
Статический блок по-прежнему имеет доступ только к статическим полям (в случае вашего enum 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 и по-прежнему разрешает доступ к переменной экземпляра внутри статического блока
Свами 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 от оракула :

Если этот метод вызывается более одного раза с одним и тем же именем библиотеки, второй и последующие вызовы игнорируются.

Так что совершенно неожиданно размещение 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");
    } 
}
Vid
источник
-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);
        }
        }

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

}

Arun
источник