静态初始化块
据我了解,“静态初始化块”是用来设置静态字段的值,如果不能在一行中完成。
但我不明白为什么我们需要一个特殊的块。 例如,我们将一个字段声明为静态的(没有赋值)。 然后写几行代码,生成并赋值给上面声明的静态字段。
为什么我们需要这样的特殊块: static {...}
?
非静态块:
{ // Do Something... }
每次构造类的实例时都会调用它。 静态块只被调用一次 ,当类本身被初始化时,不pipe你创build的types有多less个对象。
例:
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
如果它们不在静态初始化块中,它们将在哪里? 你将如何声明一个variables,只是为了初始化的目的是局部的,并将其与字段区分开来? 例如, 你想怎么写:
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
块中,那么它将被视为一个实例初始化块而不是一个静态初始化块,所以它将在每个构build的实例中执行一次,而不是一次。
现在在这种情况下,您可以使用静态方法:
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; } }
…但是,当你想在同一个块中分配多个variables时,或者没有(例如,如果你只是想logging一些东西 – 或者可能初始化一个本地库),这是行不通的。
这是一个例子:
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"); }
在“静态”部分中的代码将在类加载时执行,在构造类的任何实例之前(以及从其他地方调用任何静态方法之前)。 这样你就可以确保class级资源都可以使用。
也可以有非静态初始化块。 这些行为就像为该类定义的一组构造函数方法的扩展一样。 它们看起来就像静态初始化块,除了关键字“static”被closures。
当你实际上不想把值赋给任何东西时,这也很有用,例如在运行时只加载一次类。
例如
static { try { Class.forName("com.example.jdbc.Driver"); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError("Cannot load JDBC driver.", e); } }
嘿,还有一个好处,你可以用它来处理exception。 想象一下getStuff()
在这里会抛出一个真正属于catch块的Exception
:
private static Object stuff = getStuff(); // Won't compile: unhandled exception.
那么static
初始化器在这里很有用。 你可以在那里处理exception。
另一个例子是在分配过程中不能完成的事情:
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
注册自己。 也看到这个和这个答案。
有几个实际的原因需要存在:
- 初始化初始化可能抛出exception的
static final
成员 - 用计算值初始化
static final
成员
人们倾向于使用static {}
块作为一种便捷的方式来初始化类在运行时所依赖的事物,例如确保加载特定的类(例如,JDBC驱动程序)。 这可以通过其他方式来完成。 然而,我上面提到的两件事情只能用类似static {}
的构造来完成。
我会说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; }
在静态块中构造对象之前,您可以对类执行一次代码。
例如
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
),以将一堆别名映射回原始的枚举types。 它旨在作为Enum
本身提供的内置valueOf方法的扩展。
正如你所看到的,静态初始化器甚至访问private
字段aliases
。 更令人惊讶的是, static
块已经可以访问Enum
值实例(如ENGLISH
)。 这是因为Enum
types的初始化和执行的顺序 :
- 隐式静态字段的
Enum
常量。 这需要Enum构造函数和实例块以及实例初始化。 -
static
块和静态字段按发生顺序初始化。
欲了解更多信息,请参阅“ 有效的Java ”一书。
如果你的静态variables需要在运行时设置,那么static {...}
块是非常有用的。
例如,如果您需要将静态成员设置为存储在configuration文件或数据库中的值。
当您想要将值添加到静态Map
成员时也很有用,因为您无法在初始成员声明中添加这些值。
所以你有一个静态字段(它也被称为“类variables”,因为它属于类,而不是属于类的实例;换句话说,它与类相关联,而不是任何对象),你想要初始化它。 所以,如果你不想创build这个类的一个实例,你想操纵这个静态字段,你可以用三种方法来实现:
1-当你声明variables时只需要初始化它:
static int x = 3;
2-有一个静态初始化块:
static int x; static { x=3; }
3-有一个访问类variables并初始化的类方法(静态方法):这是上述静态块的替代方法; 你可以写一个私有的静态方法:
public static int x=initializeX(); private static int initializeX(){ return 3; }
现在为什么要使用静态初始化块而不是静态方法?
这真的取决于你的程序需要什么。 但是你必须知道静态初始化块被调用一次,类方法的唯一优点是如果你需要重新初始化类variables,以后可以重用它们。
假设你的程序中有一个复杂的数组。 你初始化它(例如使用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(Aa); // 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(Aa); // this will print 3 } }
作为补充,就像@Pointy说的那样
在“静态”部分中的代码将在类加载时执行,在构造类的任何实例之前(以及从其他地方调用任何静态方法之前)。
它应该将System.loadLibrary("I_am_native_library")
到静态块中。
static{ System.loadLibrary("I_am_a_library"); }
它将保证在相关库被加载到内存之前不调用本地方法。
根据oracle的loadLibrary :
如果使用相同的库名称多次调用此方法,则第二个和后续调用将被忽略。
所以非常意外的是,不要使用System.loadLibrary来避免多次加载库。
您首先需要了解的是您的应用程序类本身在运行时被实例化为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是1”控制台。 请注意,我没有实例化任何类。
静态块只在JVM的类加载和初始化时执行一次,即在代码中第一次引用类。
使用静态块的一个典型例子是当你支持通过它的值检索一个Enum实例时,为了做到这一点,你需要定义一个HashMap作为一个静态variables,将每个值映射到相应的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); } }
参考: 在Java中的静态关键字
静态块用于任何技术来dynamic地初始化静态数据成员,或者我们可以说静态数据成员的dynamic初始化正在使用静态块。因为非静态数据成员初始化我们有构造函数,但是我们没有任何我们可以dynamic初始化静态数据成员的地方
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将dynamic初始化..Bcoz编译器将去Solution.x它将加载类加载时的解决scheme类和静态块加载时间..所以我们能够dynamic地初始化该静态数据成员..
}