静态初始化块


265

据我了解,“静态初始化块”用于设置静态字段的值(如果无法在一行中完成的话)。

但是我不明白为什么我们需要为此设置一个特殊的块。例如,我们将字段声明为静态(无值分配)。然后编写几行代码,生成并为上面声明的静态字段分配一个值。

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


6
反馈不多,但是如果您可以清楚地陈述您的假设,从而澄清哪个答案是正确的,这将有所帮助。当我第一次阅读您的问题时,我误会了,并以为您知道{...}vs 的区别static {...}。(在这种情况下,乔恩·斯基特(Jon Skeet)肯定会更好地回答您的问题)
大卫·T

1
这个问题还不清楚。您使答题者争先恐后,对自己的意思进行了许多漫长的猜想。如何明确写出您要记住的示例静态初始化块以及您的替代方案,以便使人们有明确的答案?
唐·哈奇

Answers:


430

非静态块:

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

106
为什么这是公认的答案?它甚至没有回答问题。
Paul Bellora 2011年

43
它回答了一个问题:“每次构造类时都会调用此方法。无论您创建多少个该类型的对象,静态块都只会调用一次。”
亚当·阿罗德

83
对于好奇的读者来说,非静态块实际上是由Java编译器复制到类具有的所有构造函数中的()。因此,初始化字段仍然是构造函数的工作。
Martin Andersson

2
可接受的答案应为以下答案:stackoverflow.com/a/2420404/363573。该答案提供了一个实际示例,其中您需要静态块。
斯蒂芬

16
为什么这个答案突然被否决?您可能不同意这个被接受的答案,但是它绝对不是错误或误导的。它只是试图通过一个简单的示例来帮助理解这些语言结构。
Frederik Wordenskjold 2013年

132

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

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

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


1
静态块是在分配静态变量之前还是之后发生?private static int widgets = 0; static{widgets = 2;}
曾伟石

1
好奇静态块是在分配静态变量之前还是之后发生。例如,private static int widgets = 0; static{widgets = 2;}发现“ =”分配是按顺序发生的,这意味着放在首位的“ =”将首先分配。上面的示例将“小部件”的值设置为2。(PS不知道注释只能在5分钟内进行编辑...)
曾为时

@WeishiZeng:是的,这是因为记录在docs.oracle.com/javase/specs/jls/se8/html/... -点9
乔恩斯基特

但是,您是否也不能使用与静态初始化块具有完全相同代码的私有静态方法,并将窗口小部件分配给私有静态方法?
Zachary Kraus 2015年

1
@Zachary:您的意思是返回值,并分配方法调用的结果吗?如果是这样,是的-当你正在为块的结果分配给恰好一个变量。将在大约7小时内编辑我的答案,并提供详细信息...
Jon Skeet 2015年

103

这是一个例子:

  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”没有设置。


4
对于该特定示例,有时双括号模式被“滥用” :)
BalusC 2010年

它可以被滥用,但另一方面,它确实清除了一些混乱,并使某些代码更加“坚固”。我用Erlang编程很有趣,并且您迷上了不需要局部变量的问题:-)
Pointy 2010年

1
<<在构造类的任何实例之前(以及在从其他位置调用任何静态方法之前),将在类加载时执行“静态”部分中的代码。这样,您可以确保所有类资源都可以使用。>>(在上面的答案中提到了“ Pointy”),这对于静态块执行非常重要。
学习者2010年

我们可以在afterPropertiesSet方法之后使用InitializingBean来做到这一点吗?
egemen '19

48

当您实际上不希望将值分配给任何东西(例如在运行时加载某个类一次)时,它也很有用。

例如

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

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

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。也看到这个这个答案。


2
这里有危险的巫毒教...静态初始化程序在合成的clinit()方法中运行,该方法隐式同步。这意味着JVM将获得有关该类文件的锁。如果两个类试图互相加载,并且每个类开始在不同的线程中加载,则可能导致在多线程环境中出现死锁。请参阅www-01.ibm.com/support/docview.wss?uid=swg1IV48872
Ajax

@Ajax:我认为这是有问题的JDBC驱动程序或负责加载它的应用程序代码中的错误。通常,在使用适当的JDBC驱动程序的情况下,只要在应用程序启动期间仅在应用程序范围内将其加载一次,就没有问题了。
BalusC

但是,这肯定是一个错误,但不完全是JDBC驱动程序的错误。也许驱动程序无辜地拥有自己的静态初始化器,也许您无辜地初始化了该类以及您应用中的其他初始化类,并且,哦,不,某些意外类周期性地互相加载,现在您的应用程序死锁了。由于java.awt.AWTEvent和sun.util.logging.PlatformLogger之间的死锁,我发现了这一点。我只触摸过AWTEvent告诉它无头运行,而其他一些lib则在加载PlatformLogger ...时也加载了AWTEvent。
Ajax

1
这两个类在不同的线程上同步结束,并且我的构建死锁了大约1/150运行。因此,我现在在静态块中的类加载方面要格外小心。在上面提到的情况下,使用延迟提供程序模式,可以立即创建一个临时提供程序类(没有死锁的机会),初始化该字段,然后在实际访问该字段时(在非同步字段访问中),然后我实际上加载了可能导致死锁的类。
Ajax

11

我想说static block的只是语法糖。您无法使用staticblock进行任何操作,而不能使用其他任何操作。

重用此处发布的一些示例。

无需使用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;
}

10

有一些实际的原因要求它存在:

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

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


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

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

在这里,初始化程序用于维护索引(ALIAS_MAP),以将一组别名映射回原始的枚举类型。它旨在作为Enum自身提供的内置valueOf方法的扩展。

如您所见,静态初始化程序甚至访问privatefield aliases。重要的是要了解该static块已经可以访问Enum值实例(例如ENGLISH)。这是因为对于type而言初始化和执行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

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

只需在main方法中访问Foo,就可以加载该类并启动静态初始化。但是作为静态初始化的一部分,我们再次调用静态字段的构造函数,此后它将恢复静态初始化,并完成从main方法内部调用的构造函数。我希望在正常的编码中不必处理这种复杂的情况。

有关更多信息,请参见《有效的Java》一书。


1
拥有访问权限aliases并不意味着静态块可以访问非静态成员。aliases通过Language/ static / values()方法返回的值进行访问。正如您提到的那样,此时枚举变量已经可用这一事实是不寻常的一点-在这种情况下,常规类的非静态成员将不可访问。
Ignazio

静态块仍仅访问静态字段(对于您的枚举ENGLISH,GERMAN,...),在这种情况下为对象。由于静态字段是对象本身,因此您可以访问静态对象的实例字段。
Swami PR

1
class Foo { static final Foo Inst1; static final Foo Inst2; static{ Inst1 = new Foo("Inst1"); Inst2 = new Foo("Inst2"); } static { System.out.println("Inst1: " + Inst1.member); System.out.println("Inst2: " + Inst2.member); } private final String member; private Foo(String member){ this.member = member; } } 上面的代码与枚举示例没有什么不同,并且仍然允许访问静态块内的实例变量
Swami

确实,@ SwamiPR确实可以编译,令我惊讶的是,我必须同意代码在原则上没有什么不同。我必须重新阅读Java规范,我觉得我错过了一些东西。很好的回复,谢谢。
YoYo

@SwamiPR问题实际上是我们应该使用Enum。这是确保我们指向单个实例的最佳方法-请参见此处。关于您的观点,我进行了几次更新。
悠悠球'18

3

如果需要在运行时设置静态变量,则使用static {...}块非常有帮助。

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

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


3

因此,您有一个静态字段(也称为“类变量”,因为它属于类而不是类的实例;换句话说,它与类而不是与任何对象关联),并且您想对其进行初始化。因此,如果您不想创建此类的实例,并且想要操纵此静态字段,则可以通过以下三种方式来实现:

1-声明变量时只需对其进行初始化:

static int x = 3;

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

static int x;

static {
 x=3;
}

3-有一个访问类变量并对其进行初始化的类方法(静态方法):这是上述静态块的替代方法;您可以编写一个私有静态方法:

public static int x=initializeX();

private static int initializeX(){
 return 3;
}

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

这实际上取决于您程序中的需求。但是您必须知道静态初始化块被调用一次,并且class方法的唯一优点是,如果您需要重新初始化class变量,则可以在以后重用它们。

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

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

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

}

0

作为补充,就像@Pointy说的

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

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

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

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

根据来自oracle的loadLibrary

如果使用相同的库名称多次调用此方法,则将忽略第二个和后续调用。

因此,出乎意料的是,不使用System.loadLibrary来避免多次加载库。


0

您首先需要了解,您的应用程序类本身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”进行控制台。请注意,我还没有实例化任何类。


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

-1

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

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

现在,当编译器转到Solution.x时,我的静态int x将动态初始化..Bcoz,它将在类加载时加载解决方案类和静态块加载。.因此,我们可以动态初始化该静态数据成员。

}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.