关于java:静态初始化块

Static Initialization Blocks

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

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

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


非静态块:

1
2
3
{
    // Do Something...
}

每次构造类的实例时调用。无论您创建了多少类型的对象,在初始化类本身时,静态块只被调用一次。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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();
    }
}

印刷品:

1
2
3
Static
Non-static block
Non-static block


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

1
2
3
4
5
6
7
8
9
10
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的块中,那么该块将计为实例初始化块而不是静态初始化块,因此每个构造的实例执行一次,而不是总共执行一次。

现在,在这种特殊情况下,您可以使用静态方法来代替:

1
2
3
4
5
6
7
8
9
10
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
2
3
4
5
6
  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"被省略了。


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

例如。

1
2
3
4
5
6
7
static {
    try {
        Class.forName("com.example.jdbc.Driver");
    } catch (ClassNotFoundException e) {
        throw new ExceptionInInitializerError("Cannot load JDBC driver.", e);
    }
}

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

1
private static Object stuff = getStuff(); // Won't compile: unhandled exception.

那么,static初始值设定项在这里很有用。您可以在那里处理异常。

另一个例子是事后做一些分配过程中不能做的事情:

1
2
3
4
5
6
7
8
9
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中注册自己。也可以看到这个和这个答案。


我想说,static block只是句法上的糖分。你不能用static块做任何事情,也不能用其他任何东西。

重新使用这里发布的一些示例。

这段代码可以不用static初始化器重新编写。

方法1:使用static

1
2
3
4
5
6
private static final HashMap<String, String> MAP;
static {
    MAP.put("banana","honey");
    MAP.put("peanut butter","jelly");
    MAP.put("rice","beans");
  }

方法2:无static

1
2
3
4
5
6
7
8
9
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;
}

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

  • 初始化可能引发异常的static final成员
  • 用计算值初始化static final成员
  • 人们倾向于使用static {}块作为一种方便的方法来初始化类在运行时内依赖的东西,例如确保加载特定的类(例如JDBC驱动程序)。这可以通过其他方式实现;但是,我上面提到的两件事情只能通过一个构造(如static {}块)来实现。


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

    例如。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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());
      }
    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    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本身提供的方法的内置值的扩展。

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

  • Enum常量,是隐式静态字段。这需要枚举构造函数和实例块,以及首先进行实例初始化。
  • static块,按出现顺序初始化静态字段。
  • 这个无序初始化(static块之前的构造函数)需要注意。当我们使用类似于singleton的实例初始化静态字段时,也会发生这种情况(进行了简化):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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();
      }
    }

    我们看到的是以下输出:

    1
    2
    3
    4
    5
    Static Block 1
    Constructor
    Static Block 2
    In Main
    Constructor

    很明显,静态初始化实际上可以在构造函数之前甚至之后进行:

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

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


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

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

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


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

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

    1
    static int x = 3;

    2-具有静态初始化块:

    1
    2
    3
    4
    5
    static int x;

    static {
     x=3;
    }

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

    1
    2
    3
    4
    5
    public static int x=initializeX();

    private static int initializeX(){
     return 3;
    }

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

    这完全取决于你的计划需要什么。但是您必须知道静态初始化块被调用一次,并且类方法的唯一优点是,如果需要重新初始化类变量,可以在以后重用它们。

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

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

    例1:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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
     }

    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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");
        }
    }


    首先需要了解的是,应用程序类本身在运行时被实例化为java.class.Class对象。这是运行静态块的时间。所以你可以这样做:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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"。注意,我没有实例化任何类。


    作为补充,就像@pointy说的

    The code in the"static" section(s) will be executed at class load
    time, before any instances of the class are constructed (and before
    any static methods are called from elsewhere).

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

    1
    2
    3
    static{
        System.loadLibrary("I_am_a_library");
    }

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

    根据Oracle的LoadLibrary:

    If this method is called more than once with the same library name,
    the second and subsequent calls are ignored.

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


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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时,它将在类加载时加载解决方案类和静态块加载..这样我们可以动态初始化该静态数据成员..

    }