286

据我了解,如果不能在一行中完成,则“静态初始化块”用于设置静态字段的值。

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

为什么我们需要将这些行放在一个特殊的块中,例如:static {...}

4

14 回答 14

482

静态块:

{
    // 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
于 2010-03-10T20:48:39.087 回答
140

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

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

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

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

于 2010-03-10T20:40:49.693 回答
107

这是一个例子:

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

于 2010-03-10T20:40:27.570 回答
50

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

例如

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. 另请参阅thisthis answer。

于 2010-03-10T21:20:16.710 回答
12

我会说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;
}
于 2013-02-28T05:20:49.860 回答
10

有几个实际原因需要它存在:

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

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

于 2012-09-07T11:43:39.567 回答
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());
  }
}
于 2010-03-10T20:40:27.277 回答
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),将一组别名映射回原始枚举类型。它旨在作为其自身提供的内置 valueOf 方法的扩展Enum

如您所见,静态初始化程序甚至访问privatefield aliases。重要的是要理解static块已经可以访问Enum值实例(例如ENGLISH)。这是因为在types的情况下初始化和执行的顺序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 方法中调用的构造函数。我希望在正常编码中我们不必处理的相当复杂的情况。

有关这方面的更多信息,请参阅“ Effective Java ”一书。

于 2015-06-15T04:31:46.200 回答
4

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

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

static int x = 3;

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

static int x;

static {
 x=3;
}

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
 }

}
于 2013-07-29T03:51:15.293 回答
3

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

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

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

于 2010-03-10T20:40:51.443 回答
0

作为补充,就像@Pointy 说的

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

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

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

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

根据oracle 的 loadLibrary

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

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

于 2016-08-03T14:18:51.873 回答
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”到控制台。请注意,我没有实例化任何类。

于 2016-10-20T16:02:30.637 回答
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");
    } 
}
于 2019-05-10T05:22:58.513 回答
-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);
        }
        }

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

}

于 2012-10-14T16:45:21.590 回答