静态初始化块

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

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

为什么我们需要在像static {...}这样的特殊块中使用这些行?

248489 次浏览

在静态块中构造对象之前,可以为一个类执行一次代码。

如。

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());
}
}

这里有一个例子:

  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”被省略了。

如果它们不在静态初始化块中,它们会在哪里?如何声明一个只用于初始化的局部变量,并将其与字段区分开来?例如,想要如何写:

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的块中,这将被算作实例初始化块,而不是静态初始化块,因此它将被执行一次构造实例,而不是总共执行一次。

现在在这个特殊的情况下,你可以使用一个静态方法:

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;
}
}

... 但是当你希望在同一个块中分配多个变量时,或者没有变量时(例如,如果你只是想记录一些东西-或者可能初始化一个本机库),这就不起作用了。

如果你的静态变量需要在运行时设置,那么static {...}块是非常有用的。

例如,如果您需要将static成员设置为存储在配置文件或数据库中的值。

当你想要向静态Map成员添加值时也很有用,因为你不能在初始成员声明中添加这些值。

非静态块:

{
// 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

当你实际上不想将值赋给任何东西时,比如在运行时加载一些类只有一次,它也很有用。

如。

static {
try {
Class.forName("com.example.jdbc.Driver");
} catch (ClassNotFoundException e) {
throw new ExceptionInInitializerError("Cannot load JDBC driver.", e);
}
}

嘿,还有一个好处,你可以用它来处理异常。假设这里的getStuff()抛出了一个真的属于catch块的Exception:

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中注册自己。另见 answer。

有几个实际的原因,它必须存在:

  1. 初始化static final成员,其初始化可能引发异常
  2. 用计算值初始化static final成员

人们倾向于使用static {}块作为一种方便的方式来初始化类在运行时所依赖的东西——比如确保特定的类被加载(例如,JDBC驱动程序)。这可以通过其他方式来实现;然而,我上面提到的两件事只能用static {}块这样的构造来完成。

静态块用于任何以动态方式初始化静态数据成员的技术,或者我们可以说静态数据成员的动态初始化使用静态块,因为对于非静态数据成员初始化,我们有构造函数,但没有任何地方可以动态初始化静态数据成员

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时,编译器将去解决方案。因此,我们可以动态地初始化静态数据成员。

我会说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;
}

所以你有一个静态字段(它也被称为“类变量”,因为它属于类而不是类的实例;换句话说,它与类而不是与任何对象相关联),你想初始化它。所以如果你不想创建这个类的实例,你想操纵这个静态字段,你可以用三种方式来做:

1-在声明变量时初始化它:

static int x = 3;

2-有一个静态初始化块:

static int x;


static {
x=3;
}
有一个类方法(静态方法)来访问类变量并初始化它: 这是上述静态块的替代方案;你可以写一个私有静态方法:

public static int x=initializeX();


private static int initializeX(){
return 3;
}

为什么要用静态初始化块而不是静态方法呢?

这取决于你的项目需要什么。但是你必须知道静态初始化块只被调用一次,类方法的唯一优点是,如果你需要重新初始化类变量,它们可以被重用。

假设在程序中有一个复杂的数组。你初始化它(例如使用循环),然后这个数组中的值将在整个程序中改变,但在某个时候你想重新初始化它(回到初始值)。在这种情况下,您可以调用私有静态方法。如果你不需要在程序中重新初始化这些值,你可以只使用静态块,不需要静态方法,因为你以后不会在程序中使用它。

注意:静态块是按照它们在代码中出现的顺序调用的。

示例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
}


}

认为静态块只能访问静态字段是一种常见的误解。为此,我想展示下面一段我在现实项目中经常使用的代码(在略有不同的上下文中部分复制自另一个答案):

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类型。它的目的是作为Enum本身提供的内置valueOf方法的扩展。

如你所见,静态初始化器甚至可以访问private字段aliases。重要的是要理解static块已经可以访问Enum值实例(例如ENGLISH)。这是因为Enum类型的初始化和执行顺序,就像static private字段在调用static块之前已经用实例初始化一样:

  1. Enum常量是隐式静态字段。这需要Enum构造函数和实例块,并且实例初始化也首先发生。
  2. 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,会导致类被加载并开始静态初始化。但是作为静态初始化的一部分,我们再次调用静态字段的构造函数,之后它恢复静态初始化,并完成从主方法中调用的构造函数。相当复杂的情况,我希望在正常的编码中我们不需要处理。

有关更多信息,请参阅“有效的Java”这本书。

作为补充,就像@Pointy说的

“静态”部分中的代码将在类加载时执行 时间,在构造类的任何实例之前(以及在 任何静态方法都从其他地方调用)

它应该将System.loadLibrary("I_am_native_library")添加到静态块中。

static{
System.loadLibrary("I_am_a_library");
}

它将保证在相关库加载到内存之前不调用本机方法。

根据从oracle加载库:

如果这个方法被同一个库名调用多次,

.

.

.

所以很意外地,把系统。不使用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 is 1打印到控制台。

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");
}
}