据我所知,“静态初始化块”是用来设置静态字段的值,如果它不能在一行中完成。
但我不明白为什么我们需要一种特殊的积木。例如,我们将一个字段声明为静态(没有赋值)。然后写几行代码,生成并赋值给上面声明的静态字段。
为什么我们需要这些行在一个特殊的块,如:static{…}?
据我所知,“静态初始化块”是用来设置静态字段的值,如果它不能在一行中完成。
但我不明白为什么我们需要一种特殊的积木。例如,我们将一个字段声明为静态(没有赋值)。然后写几行代码,生成并赋值给上面声明的静态字段。
为什么我们需要这些行在一个特殊的块,如:static{…}?
当前回答
有几个实际的原因,它必须存在:
初始化静态final成员,其初始化可能引发异常 用计算值初始化静态final成员
人们倾向于使用静态{}块作为一种方便的方式来初始化类在运行时所依赖的东西——比如确保特定的类被加载(例如JDBC驱动程序)。这可以通过其他方式来实现;然而,我上面提到的两件事只能用静态{}块这样的构造来完成。
其他回答
认为静态块只能访问静态字段是一种常见的误解。为此,我想展示下面一段我在现实项目中经常使用的代码(部分复制自另一个略有不同的上下文的答案):
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),将一组别名映射回原始enum类型。它的目的是作为枚举本身提供的内置valueOf方法的扩展。
如您所见,静态初始化器甚至可以访问私有字段别名。重要的是要理解静态块已经可以访问Enum值实例(例如ENGLISH)。这是因为在Enum类型的情况下,初始化和执行的顺序,就像静态私有字段在调用静态块之前已经被实例初始化一样:
Enum常量是隐式静态字段。这需要Enum构造函数和实例块,并且实例初始化也首先发生。 静态块和初始化静态字段的出现顺序。
注意这种乱序初始化(构造函数在静态块之前)非常重要。当我们用类似于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,会导致类被加载并开始静态初始化。但是作为静态初始化的一部分,我们再次调用静态字段的构造函数,之后它恢复静态初始化,并完成从主方法中调用的构造函数。相当复杂的情况,我希望在正常的编码中我们不需要处理。
有关这方面的更多信息,请参阅《Effective Java》一书。
如果它们不在静态初始化块中,它们会在哪里?如何声明一个只用于初始化的局部变量,并将其与字段区分开来?例如,你想怎么写:
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;
}
}
如果第一个和第二个不在一个块中,它们看起来就像字段。如果它们在一个前面没有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;
}
}
... 但是当你希望在同一个块中分配多个变量时,或者没有变量时(例如,如果你只是想记录一些东西-或者可能初始化一个本机库),这就不起作用了。
当您实际上不想将值赋给任何东西时,例如在运行时只加载某个类一次时,它也很有用。
E.g.
static {
try {
Class.forName("com.example.jdbc.Driver");
} catch (ClassNotFoundException e) {
throw new ExceptionInInitializerError("Cannot load JDBC driver.", e);
}
}
嘿,还有一个好处,你可以用它来处理异常。想象一下getStuff()在这里抛出了一个异常,该异常实际上属于一个catch块:
private static Object stuff = getStuff(); // Won't compile: unhandled 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驱动程序本身也使用静态初始化器在DriverManager中注册自己。还有这个和这个答案。
静态块用于任何以动态方式初始化静态数据成员的技术,或者我们可以说静态数据成员的动态初始化使用静态块,因为对于非静态数据成员初始化,我们有构造函数,但没有任何地方可以动态初始化静态数据成员
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将动态初始化..Bcoz时,编译器将去解决方案。因此,我们可以动态地初始化静态数据成员。
}
所以你有一个静态字段(它也被称为“类变量”,因为它属于类而不是类的实例;换句话说,它与类而不是与任何对象相关联),你想初始化它。所以如果你不想创建这个类的实例,你想操纵这个静态字段,你可以用三种方式来做:
1-在声明变量时初始化它:
static int x = 3;
2-有一个静态初始化块:
static int x;
static {
x=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
}
}