静态初始化块

据我了解,“静态初始化块”是用来设置静态字段的值,如果不能在一行中完成。

但我不明白为什么我们需要一个特殊的块。 例如,我们将一个字段声明为静态的(没有赋值)。 然后写几行代码,生成并赋值给上面声明的静态字段。

为什么我们需要这样的特殊块: 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; } } 

如果firstsecond不在一个街区,他们会看起来像田野。 如果它们在前面没有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注册自己。 也看到这个和这个答案。

有几个实际的原因需要存在:

  1. 初始化初始化可能抛出exception的static final成员
  2. 用计算值初始化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 )。 这是因为Enumtypes的初始化和执行的顺序 :

  1. 隐式静态字段的Enum常量。 这需要Enum构造函数和实例块以及实例初始化。
  2. 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地初始化该静态数据成员..

}