静态初始化代码块和非静态初始化代码块有什么区别?

380

我的问题是关于static关键字的一个特定用法。可以使用static关键字覆盖类中不属于任何函数的代码块。例如,以下代码可以编译:

我的问题是关于static关键字的一个特定用法。可以使用static关键字来覆盖类中不属于任何函数的代码块。例如,以下代码可编译:

public class Test {
    private static final int a;    
    static {
        a = 5;
        doSomething(a);
    }
    private static int doSomething(int x) {
        return (x+5);
    }
}

如果您删除static关键字,编译器会报错,因为变量afinal的。然而,如果同时删除finalstatic关键字,那么它可以编译通过。

这让我感到困惑。我该如何创建一个不属于任何方法的代码段?它怎么可能被调用?总的来说,这个用法的目的是什么?或者更好地说,我在哪里可以找到相关文档?

9个回答

426

具有静态修饰符的代码块表示一个类初始化程序;没有静态修饰符的代码块是一个实例初始化程序。

当类被加载时(实际上是解析时,但这只是技术问题),按照定义的顺序(自上而下,就像简单变量初始化器一样)执行类初始化器。

在实例化类时,按照定义的顺序立即在构造函数代码执行之前、调用超类构造函数之后执行实例初始化器。

如果从 int a 中删除 static,它将成为一个实例变量,您无法从静态初始化程序块中访问它。这将导致编译错误“无法从静态上下文引用非静态变量a”。

如果还从初始化器块中删除了 static,则它变成了实例初始化器,因此会在构建时初始化 int a


1
静态初始化程序实际上是在类被加载和链接后,当您实例化一个类的对象或访问类上的静态变量或方法时才被调用。事实上,如果您有一个带有静态初始化程序和方法public static void staticMethod() {}的类,如果您执行TestStatic.class.getMethod("staticMethod"),则不会调用静态初始化程序。更多信息请参见https://docs.oracle.com/javase/specs/jvms/se10/html/jvms-5.html#jvms-5.5。 - Totò
@Totò:是的,这就是类的分辨率所包含的内容(至少在以前他们称之为链接+初始化时是“分辨率”)。我并不惊讶你可以使用反射来发现关于一个类的东西,而不需要它解决。 - Lawrence Dol

191

Uff! 静态初始化器是什么?

静态初始化器是 Java 类中的一个 static {} 代码块,只在构造函数或主方法调用之前运行一次。

好的,再告诉我一些细节...

  • 是任何 Java 类中的 static { ... } 代码块,并且在类被调用时由虚拟机执行。
  • 不支持 return 语句。
  • 不支持参数。
  • 不支持 thissuper

嗯,我在哪里可以使用它?

你可以在任何你认为合适的地方使用它 :) 很简单。但我看到大多数情况下它被用于数据库连接、API 初始化、日志记录等。

别啰嗦了!给我个例子呗?

package com.example.learnjava;

import java.util.ArrayList;

public class Fruit {

    static {
        System.out.println("Inside Static Initializer.");

        // fruits array
        ArrayList<String> fruits = new ArrayList<>();
        fruits.add("Apple");
        fruits.add("Orange");
        fruits.add("Pear");

        // print fruits
        for (String fruit : fruits) {
            System.out.println(fruit);
        }
        System.out.println("End Static Initializer.\n");
    }

    public static void main(String[] args) {
        System.out.println("Inside Main Method.");
    }
}

输出???

静态初始化器内部。

苹果

橘子

结束静态初始化器。

主方法内部。


谢谢Madan!静态块能否代替InitializingBeanafterPropertiesSet()方法? - Alexander Suraphel
3
可以!当类被 JVM 加载时,静态初始化程序会被调用。因此,这是代码执行的真正第一阶段。如果您还有构造函数,则顺序为:静态初始化程序、构造函数、afterPropertiesSet。 - Martin Baumgartner

62

static块是一个"静态初始化器"。

当类加载时,它会自动被调用,没有其他方法可以调用它(甚至反射也不行)。

我个人只在编写JNI代码时使用过它:

class JNIGlue {
    static {
        System.loadLibrary("foo");
    }
}

6
没有明确的方法来调用它,类初始化器不会被表示为Method实例,而是只有在Java虚拟机中被调用。 - Rafael Winterhalter

53

以下内容直接来自http://www.programcreek.com/2011/10/java-class-instance-initializers/

1. 执行顺序

看下面这个类,你知道哪一个会先执行吗?

public class Foo {
 
    //instance variable initializer
    String s = "abc";
 
    //constructor
    public Foo() {
        System.out.println("constructor called");
    }
 
    //static initializer
    static {
        System.out.println("static initializer called");
    }
 
    //instance initializer
    {
        System.out.println("instance initializer called");
    }
 
    public static void main(String[] args) {
        new Foo();
        new Foo();
    }
}

输出:

静态初始化程序被调用

实例初始化程序被调用

构造函数被调用

实例初始化程序被调用

构造函数被调用

2. Java实例初始化程序是如何工作的?

上面的实例初始化程序包含一个println语句。为了理解它的工作原理,我们可以将其视为变量赋值语句,例如b = 0。这可以使它更容易理解。

int b = 0不同,您可以编写

int b;
b = 0;
因此,实例初始化程序和实例变量初始化程序基本相同。 3. 何时使用实例初始化器? 尽管使用实例初始化器不常见,但如果: 1. 初始化代码必须处理异常 2. 执行无法通过实例变量初始化器表达的计算。 当然,这样的代码可以写在构造函数中。但是,如果一个类有多个构造函数,就必须在每个构造函数中重复这些代码。 使用实例初始化器,只需编写一次代码,它将在创建对象时无论使用哪个构造函数都会被执行。(我猜这只是一个概念,并且并不经常使用。) 实例初始化器有用的另一种情况是匿名内部类,它们根本不能声明任何构造函数。(将这作为放置日志函数的好地方吗?) 感谢Derhein。 还要注意,实现接口的匿名类[1]没有构造函数。因此,需要实例初始化器来执行任何类型的表达式以在构造时执行。

14

"final"保证一个变量在对象初始化代码结束之前必须被初始化。同样,“static final”保证一个变量将在类初始化代码结束前被初始化。省略初始化代码中的“static”会将其转换为对象初始化代码;因此,您的变量不再满足其保证。


10

不要在任何程序中需要调用的静态块中编写代码。如果代码需要被调用,则必须将其放置在方法中。

您可以编写静态初始化块来初始化静态变量,当类被加载时,但这些代码可能更加复杂。

静态初始化块看起来像没有名称、没有参数和没有返回类型的方法。由于您从未调用它,因此它不需要名称。唯一调用它的时机是虚拟机加载类。


7
当开发人员使用初始化块时,Java编译器会将初始化程序复制到当前类的每个构造函数中。
示例:
以下代码:
class MyClass {

    private int myField = 3;
    {
        myField = myField + 2;
        //myField is worth 5 for all instance
    }

    public MyClass() {
        myField = myField * 4;
        //myField is worth 20 for all instance initialized with this construtor
    }

    public MyClass(int _myParam) {
        if (_myParam > 0) {
            myField = myField * 4;
            //myField is worth 20 for all instance initialized with this construtor
            //if _myParam is greater than 0
        } else {
            myField = myField + 5;
            //myField is worth 10 for all instance initialized with this construtor
            //if _myParam is lower than 0 or if _myParam is worth 0
        }
    }

    public void setMyField(int _myField) {
        myField = _myField;
    }


    public int getMyField() {
        return myField;
    }
}

public class MainClass{

    public static void main(String[] args) {
        MyClass myFirstInstance_ = new MyClass();
        System.out.println(myFirstInstance_.getMyField());//20
        MyClass mySecondInstance_ = new MyClass(1);
        System.out.println(mySecondInstance_.getMyField());//20
        MyClass myThirdInstance_ = new MyClass(-1);
        System.out.println(myThirdInstance_.getMyField());//10
    }
}

相当于:

class MyClass {

    private int myField = 3;

    public MyClass() {
        myField = myField + 2;
        myField = myField * 4;
        //myField is worth 20 for all instance initialized with this construtor
    }

    public MyClass(int _myParam) {
        myField = myField + 2;
        if (_myParam > 0) {
            myField = myField * 4;
            //myField is worth 20 for all instance initialized with this construtor
            //if _myParam is greater than 0
        } else {
            myField = myField + 5;
            //myField is worth 10 for all instance initialized with this construtor
            //if _myParam is lower than 0 or if _myParam is worth 0
        }
    }

    public void setMyField(int _myField) {
        myField = _myField;
    }


    public int getMyField() {
        return myField;
    }
}

public class MainClass{

    public static void main(String[] args) {
        MyClass myFirstInstance_ = new MyClass();
        System.out.println(myFirstInstance_.getMyField());//20
        MyClass mySecondInstance_ = new MyClass(1);
        System.out.println(mySecondInstance_.getMyField());//20
        MyClass myThirdInstance_ = new MyClass(-1);
        System.out.println(myThirdInstance_.getMyField());//10
    }
}

希望开发者能够理解我的示例。


5
静态代码块可以用于实例化或初始化类变量(与对象变量相对)。因此,声明 "a" 为静态意味着所有 Test 对象共享这个变量,并且静态代码块只在第一次加载 Test 类时初始化 "a",无论创建多少个 Test 对象。

作为后续,如果我不创建对象实例而是调用一个公共静态函数。这是意味着这个块在这个公共函数调用之前保证执行吗?谢谢。 - Szere Dyeri
如果您调用类的公共静态函数,则需要先加载该类,因此是的,静态初始化程序将首先执行。 - Paul Tomblin
除非是类初始化间接调用了试图使用它的代码。IFYSWIM。循环依赖等等。 - Tom Hawtin - tackline
1
@Tom是正确的 - 可以编写一些东西,在其中一个静态初始化程序调用另一个静态初始化程序之前调用静态方法,但我想到这个想法时就会感到恶心,所以从未考虑过。 - Paul Tomblin

0

静态初始化块在JVM将类加载到内存中并在主方法之前被调用(按照定义的顺序)。它用于有条件地初始化静态变量。

同样,我们还有实例初始化块(也称为IIB),它们在对象实例化时被调用,并且通常用于去重构造函数逻辑。

初始化程序和构造函数执行的顺序是:

  1. 静态初始化块;
  2. 对象初始化块;
  3. 构造函数;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接