Static Initialization Blocks
据我所知,"静态初始化块"用于设置静态字段的值(如果不能在一行中完成)。
但我不明白为什么我们需要一个特殊的街区。例如,我们声明一个字段是静态的(没有赋值)。然后写几行代码,生成一个值并分配给上面声明的静态字段。
为什么我们需要这样一个特殊的块中的行:
非静态块:
1 2 3 | { // Do Something... } |
每次构造类的实例时调用。无论您创建了多少类型的对象,在初始化类本身时,静态块只被调用一次。
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
印刷品:
1 2 3 | Static Non-static block Non-static block |
如果它们不在静态初始化块中,它们将在哪里?您将如何声明一个仅用于初始化的本地变量,并将其与字段区分开来?例如,您希望如何编写:
1 2 3 4 5 6 7 8 9 10 | 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; } } |
如果
现在,在这种特殊情况下,您可以使用静态方法来代替:
1 2 3 4 5 6 7 8 9 10 | 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 2 3 4 5 6 |
"静态"部分中的代码将在类加载时、类的任何实例构造之前(以及从其他地方调用任何静态方法之前)执行。这样可以确保类资源都可以使用。
也可以使用非静态初始值设定项块。这些方法类似于为类定义的一组构造函数方法的扩展。它们看起来就像静态初始值设定项块,只不过关键字"static"被省略了。
当您实际上不想将值赋给任何东西(比如在运行时只加载一次类)时,它也很有用。
例如。
1 2 3 4 5 6 7 | static { try { Class.forName("com.example.jdbc.Driver"); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError("Cannot load JDBC driver.", e); } } |
嘿,还有一个好处,你可以用它来处理异常。假设
1 |
那么,
另一个例子是事后做一些分配过程中不能做的事情:
1 2 3 4 5 6 7 8 9 | 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驱动程序本身也使用
我想说,
重新使用这里发布的一些示例。
这段代码可以不用
方法1:使用
1 2 3 4 5 6 | private static final HashMap<String, String> MAP; static { MAP.put("banana","honey"); MAP.put("peanut butter","jelly"); MAP.put("rice","beans"); } |
方法2:无
1 2 3 4 5 6 7 8 9 |
需要它存在的实际原因有几个:
人们倾向于使用
在静态块中构造对象之前,可以为类执行一次代码位。
例如。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
认为静态块只能访问静态字段是一种常见的误解。为此,我想在下面展示一段我在现实项目中经常使用的代码(在稍微不同的上下文中从另一个答案部分复制):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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); } } |
这里,初始值设定项用于维护索引(
如您所见,静态初始值设定项甚至访问
这个无序初始化(
1 2 3 4 5 6 7 8 9 10 |
我们看到的是以下输出:
1 2 3 4 5 |
很明显,静态初始化实际上可以在构造函数之前甚至之后进行:
只需在主方法中访问foo,就可以加载类并启动静态初始化。但是,作为静态初始化的一部分,我们再次调用静态字段的构造函数,然后恢复静态初始化,并完成从主方法中调用的构造函数。相当复杂的情况,我希望在正常的编码中我们不必处理。
有关这方面的更多信息,请参阅"有效Java"一书。
如果需要在运行时设置静态变量,那么
例如,如果需要将静态成员设置为存储在配置文件或数据库中的值。
当您想向静态
所以您有一个静态字段(它也被称为"类变量",因为它属于类而不是类的一个实例;换句话说,它与类关联,而不是与任何对象关联),并且您想要初始化它。因此,如果不希望创建此类的实例,并且希望操纵此静态字段,可以通过三种方式进行操作:
1-只需在声明变量时初始化它:
1 | static int x = 3; |
2-具有静态初始化块:
1 2 3 4 5 | static int x; static { x=3; } |
3-有一个访问类变量并初始化它的类方法(静态方法):这是上述静态块的替代方案;您可以编写私有静态方法:
1 2 3 4 5 | public static int x=initializeX(); private static int initializeX(){ return 3; } |
现在,为什么要使用静态初始化块而不是静态方法?
这完全取决于你的计划需要什么。但是您必须知道静态初始化块被调用一次,并且类方法的唯一优点是,如果需要重新初始化类变量,可以在以后重用它们。
假设您的程序中有一个复杂的数组。您初始化它(例如使用for循环),然后这个数组中的值将在整个程序中发生更改,但在某个时刻您希望重新初始化它(返回初始值)。在这种情况下,可以调用私有静态方法。如果您不需要在程序中重新初始化这些值,您可以只使用静态块,而不需要静态方法,因为您以后不会在程序中使用它。
注意:静态块是按照它们在代码中出现的顺序调用的。
例1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
首先需要了解的是,应用程序类本身在运行时被实例化为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
它会在控制台上打印"myint is 1"。注意,我没有实例化任何类。
作为补充,就像@pointy说的
The code in the"static" section(s) will be executed at class load
time, before any instances of the class are constructed (and before
any static methods are called from elsewhere).
它应该将
1 2 3 |
它将保证在相关库加载到内存之前不会调用本机方法。
根据Oracle的LoadLibrary:
If this method is called more than once with the same library name,
the second and subsequent calls are ignored.
因此,出乎意料的是,不使用放置System.LoadLibrary来避免多次加载库。
静态块用于以动态方式初始化静态数据成员的任何技术,或者我们可以说静态数据成员的动态初始化正在使用静态块..因为对于非静态数据成员初始化,我们有构造函数,但没有任何地方可以动态初始化静态数据成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
现在,我的静态int x将动态初始化..bcoz,当编译器转到solution.x时,它将在类加载时加载解决方案类和静态块加载..这样我们可以动态初始化该静态数据成员..
}