静态初始化块

Rom*_*man 252 java static initialization static-block initialization-block

据我所知,"静态初始化块"用于设置静态字段的值,如果不能在一行中完成的话.

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

为什么我们需要在一个特殊的块这样的行这样的:static {...}

Fre*_*old 410

非静态块:

{
    // Do Something...
}
Run Code Online (Sandbox Code Playgroud)

每次构造类的实例时都会调用它.在静态块只被调用一次,当类本身初始化,无论你如何创建该类型的许多对象.

例:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这打印:

Static
Non-static block
Non-static block
Run Code Online (Sandbox Code Playgroud)

  • 为什么这是公认的答案?它甚至没有回答这个问题. (101认同)
  • 对于好奇的读者来说,非静态块实际上是由Java编译器复制到该类所具有的每个构造函数中的([source](http://docs.oracle.com/javase/tutorial/java/javaOO/initial.html) ).因此,初始化字段仍然是构造函数的工作. (80认同)
  • 它回答了这个问题:"每次构造类时都会调用它.静态块只会被调用一次,无论你创建的那种类型的对象有多少." (43认同)
  • 为什么这个答案突然被忽视?你可能不同意这是接受的答案,但它肯定没有任何错误或误导.它只是试图用一个简单的例子来帮助理解这些语言结构. (14认同)
  • 接受的答案应该是这个:http://stackoverflow.com/a/2420404/363573.这个答案提供了一个真实的例子,你需要静态块. (2认同)

Jon*_*eet 128

如果它们不在静态初始化块中,它们会在哪里?为了初始化的目的,您如何声明一个仅仅是本地的变量,并将其与字段区分开来?例如,想怎么写:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

如果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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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


Poi*_*nty 100

这是一个例子:

  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");
  }
Run Code Online (Sandbox Code Playgroud)

"静态"部分中的代码将在类加载时执行,然后构造类的任何实例(并且在从其他地方调用任何静态方法之前).这样您就可以确保类资源都可以使用了.

也可以使用非静态初始化程序块.这些行为类似于为类定义的构造函数方法集的扩展.它们看起来就像静态初始化块一样,除了关闭"static"关键字.

  • 对于那个特定的例子,有时*double brace*模式被"滥用":) (4认同)

Bal*_*usC 48

当您实际上不想将值分配给任何内容时,它也很有用,例如在运行时加载一些类.

例如

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

嘿,还有另一个好处,你可以用它来处理异常.试想一下,getStuff()在这里抛出Exception真的属于一个catch块:

private static Object stuff = getStuff(); // Won't compile: unhandled exception.
Run Code Online (Sandbox Code Playgroud)

那么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);
    }
}
Run Code Online (Sandbox Code Playgroud)

回到JDBC驱动程序示例,任何体面的JDBC驱动程序本身也会利用static初始化程序在其中注册自己DriverManager.另见这个这个答案.

  • 这里有危险的伏都教...静态初始化器在合成的clinit()方法中运行,*隐式同步*.这意味着JVM将获取对相关类文件的锁定.如果两个类尝试相互加载,并且每个类开始在不同的线程中加载,这可能导致多线程环境中的死锁.请参阅http://www-01.ibm.com/support/docview.wss?uid=swg1IV48872 (2认同)

use*_*893 11

我想说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");
  }
Run Code Online (Sandbox Code Playgroud)

方法#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;
}
Run Code Online (Sandbox Code Playgroud)


D.S*_*ley 10

存在一些需要存在的实际原因:

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

人们倾向于使用static {}块作为初始化类在运行时内所依赖的东西的便捷方式 - 例如确保加载特定类(例如,JDBC驱动程序).这可以通过其他方式完成; 但是,我上面提到的两件事只能用像static {}块这样的结构来完成.


Pie*_*tte 8

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

例如

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());
  }
}
Run Code Online (Sandbox Code Playgroud)


YoY*_*oYo 7

认为静态块只能访问静态字段是一种常见的误解.为此,我想在下面的代码片段中展示我经常在现实生活中使用的代码(在稍微不同的上下文中从另一个答案部分复制):

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); 
  } 
} 
Run Code Online (Sandbox Code Playgroud)

这里初始化器用于维护索引(ALIAS_MAP),以将一组别名映射回原始枚举类型.它旨在作为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();
  }
}
Run Code Online (Sandbox Code Playgroud)

我们看到的是以下输出:

Static Block 1
Constructor
Static Block 2
In Main
Constructor
Run Code Online (Sandbox Code Playgroud)

清楚的是,静态初始化实际上可以构造函数之前发生,甚至在:

只需在main方法中访问Foo,就会导致加载类并启动静态初始化.但是作为静态初始化的一部分,我们再次调用静态字段的构造函数,之后它恢复静态初始化,并完成从main方法中调用的构造函数.相当复杂的情况,我希望在正常编码中我们不必处理.

有关这方面的更多信息,请参阅" Effective Java " 一书.