Статические блоки инициализации
Насколько я понял, "статический блок инициализации" используется для установки значений статического поля, если это невозможно сделать в одной строке.
Но я не понимаю, зачем нам для этого нужен специальный блок. Например, мы объявляем поле как статическое (без присвоения значения). А затем напишите несколько строк кода, которые генерируют и присваивают значение объявленному выше статическому полю.
Зачем нам нужны эти строки в специальном блоке вроде: static {...}
?
13 ответов
Нестатический блок:
{
// 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
Если бы они не были в статическом блоке инициализации, где бы они были? Как бы вы объявили переменную, которая должна была быть только локальной для целей инициализации, и отличали бы ее от поля? Например, как бы вы хотели написать:
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;
}
}
... но это не работает, когда есть несколько переменных, которые вы хотите назначить в одном блоке, или ни одной (например, если вы просто хотите что-то записать - или, может быть, инициализировать нативную библиотеку).
Вот пример:
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" не указано.
Это также полезно, когда вы на самом деле не хотите присваивать значение чему-либо, например, загружать некоторый класс только один раз во время выполнения.
Например
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
, Также см. Этот и этот ответ.
Я бы сказал 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;
}
Есть несколько фактических причин, по которым он необходим:
- инициализация
static final
члены, инициализация которых может вызвать исключение - инициализация
static final
члены с рассчитанными значениями
Люди склонны использовать static {}
блоки как удобный способ инициализации вещей, от которых зависит класс, также во время выполнения - например, обеспечение загрузки определенного класса (например, драйверов JDBC). Это может быть сделано другими способами; тем не менее, две вещи, которые я упомянул выше, могут быть выполнены только с помощью такой конструкции, как static {}
блок.
Вы можете выполнить биты кода один раз для класса, прежде чем объект будет создан в статических блоках.
Например
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());
}
}
Распространено заблуждение, что статический блок имеет доступ только к статическим полям. Для этого я хотел бы показать ниже фрагмент кода, который я довольно часто использую в реальных проектах (частично скопированный из другого ответа в несколько ином контексте):
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
), чтобы сопоставить набор псевдонимов с исходным типом перечисления. Он предназначен как расширение встроенного метода valueOf, предоставляемого Enum
сам.
Как видите, статический инициализатор обращается даже к private
поле aliases
, Важно понимать, что static
блок уже имеет доступ к Enum
экземпляры значений (например, ENGLISH
). Это потому, что порядок инициализации и выполнения в случае Enum
типы, как будто static private
поля были инициализированы с экземплярами до static
блоки были названы:
-
Enum
константы, которые являются неявными статическими полями. Это требует, чтобы конструктор Enum и блоки экземпляра, а также инициализация экземпляра выполнялись первыми. 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- Просто инициализируйте его, когда объявляете переменную:
static int x = 3;
2- иметь статический блок инициализации:
static int x;
static {
x=3;
}
3. Иметь метод класса (статический метод), который обращается к переменной класса и инициализирует ее: это альтернатива вышеуказанному статическому блоку; Вы можете написать приватный статический метод:
public static int x=initializeX();
private static int initializeX(){
return 3;
}
Теперь, почему вы используете статический блок инициализации вместо статических методов?
Это действительно зависит от того, что вам нужно в вашей программе. Но вы должны знать, что статический инициализирующий блок вызывается один раз, и единственное преимущество метода класса состоит в том, что они могут быть повторно использованы позже, если вам нужно повторно инициализировать переменную класса.
скажем, у вас есть сложный массив в вашей программе. Вы инициализируете его (например, используя цикл for), а затем значения в этом массиве изменятся во всей программе, но затем в какой-то момент вы захотите повторно инициализировать его (вернитесь к исходному значению). В этом случае вы можете вызвать закрытый статический метод. Если в вашей программе не требуется повторная инициализация значений, вы можете просто использовать статический блок и не нуждаться в статическом методе, так как вы не собираетесь использовать его позже в программе.
Примечание: статические блоки вызываются в порядке их появления в коде.
Пример 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
}
}
Если ваши статические переменные должны быть установлены во время выполнения, тогда static {...}
Блок очень полезен.
Например, если вам нужно установить для статического члена значение, которое хранится в файле конфигурации или базе данных.
Также полезно, когда вы хотите добавить значения в статический Map
член, поскольку вы не можете добавить эти значения в первоначальном объявлении члена.
Сначала вы должны понять, что сами классы вашего приложения создаются 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" на консоль. Обратите внимание, что я не создал ни одного класса.
В качестве дополнительного, как сказал @Pointy
Код в "статических" разделах будет выполняться во время загрузки класса, до того, как будут созданы какие-либо экземпляры класса (и до того, как какие-либо статические методы будут вызваны из других источников).
Это должно добавить System.loadLibrary("I_am_native_library")
в статический блок.
static{
System.loadLibrary("I_am_a_library");
}
Это гарантирует, что ни один собственный метод не будет вызван до того, как соответствующая библиотека будет загружена в память.
Согласно loadLibrary от оракула:
Если этот метод вызывается более одного раза с одним и тем же именем библиотеки, второй и последующие вызовы игнорируются.
Поэтому совершенно неожиданно, размещение System.loadLibrary не используется, чтобы избежать многократной загрузки библиотеки.
Статические блоки выполняются только один раз во время загрузки и инициализации класса JVM, т.е. при первом обращении к классу в коде.
Типичный пример использования статического блока - когда вы поддерживаете извлечение экземпляра Enum по его значению, для этого вы определяете hashmap как статическую переменную, которая отображает каждое значение на соответствующий ему экземпляр Enum, карта инициализируется и заполняется внутри статического блок до того, как Enum когда-либо будет использоваться в приложении.
public enum ErrorCodes {
BUSINESS_ERROR(100), SERVER_ERROR(500), NETWORK_ERROR(1000);
private int errorCode;
// This field maps each error code numeric value to a corresponding Enum instance.
private static Map<Integer, ErrorCodes> errorCodeByErrorNumber = new HashMap<Integer, ErrorCodes>();
static {
for (ErrorCodes errorCode : ErrorCodes.values()) {
errorCodeByErrorNumber.put(errorCode.getErrorCode(), errorCode);
}
}
private ErrorCodes(int errorCode) {
this.errorCode = errorCode;
}
public int getErrorCode() {
return errorCode;
}
public static ErrorCodes getErrorCodeByNumber(Integer dayNumber) {
return errorCodeByErrorNumber.get(dayNumber);
}
}
Статический блок используется для любой технологии для динамической инициализации члена статических данных, или мы можем сказать, что для динамической инициализации члена статических данных используется статический блок. Потому что для инициализации не статического члена данных у нас есть конструктор, но у нас нет любое место, где мы можем динамически инициализировать статический член данных
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, он загрузит класс решения и загрузку статического блока во время загрузки класса.. Так что мы можем динамически инициализировать этот статический член данных..
}
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");
}
}