什么是初始化块?

129
我们可以将代码放在构造函数、方法或初始化块中。那么初始化块有什么用呢?每个Java程序都必须拥有它吗?

您的意思是在构造函数之前或之后调用init()函数吗?还是在任何方法之外声明的静态块? - atk
我无法清楚地看到你的问题是什么,也许标题有点误导。 - Junior Mayhé
可能是Java中初始化程序与构造函数的使用的重复问题。 - Mark Peters
10个回答

222

首先,有两种类型的初始化块:

  • 实例初始化块,和
  • 静态初始化块

以下代码应该说明它们的用法以及执行顺序:

public class Test {
    
    static int staticVariable;
    int nonStaticVariable;        

    // Static initialization block:
    // Runs once (when the class is initialized)
    static {
        System.out.println("Static initalization.");
        staticVariable = 5;
    }
    
    // Instance initialization block:
    // Runs each time you instantiate an object
    {
        System.out.println("Instance initialization.");
        nonStaticVariable = 7;
    }
    
    public Test() {
        System.out.println("Constructor.");
    }
    
    public static void main(String[] args) {
        new Test();
        new Test();
    }
}

输出:

Static initalization.
Instance initialization.
Constructor.
Instance initialization.
Constructor.

如果你想要在无论使用哪个构造函数时都运行某些代码,或者想要为匿名类进行一些实例初始化,那么实例初始化块非常有用。


13
目前看起来它们是按照在代码中出现的顺序执行。这个例子可以改进,使得代码中的顺序与实际执行顺序不同。此外,可能会有几个初始化块,然后它们按照出现的顺序执行(但仍在构造函数之前)。 - Thomas Weller
@Pacerier 当你有多个构造函数时,可以使用公共代码,而不必使用init()方法(某些更新类的人可能会忘记调用它)。 - pablisco
@Thomas weller,如果它在构造函数之前执行,为什么允许在实例初始化块内使用this关键字。 this是当前类对象,在构造函数调用完成后将完全构造对吧? - amarnath harish

112

我想在@aioobe的回答上添加一些内容

执行顺序:

  1. 父类的静态初始化块

  2. 类的静态初始化块

  3. 父类的实例初始化块

  4. 父类的构造函数

  5. 类的实例初始化块

  6. 类的构造函数。

需要注意的几点(第1点是@aioobe的回答的重述):

  1. 静态初始化块中的代码将在类加载时执行(是的,这意味着每个类只加载一次),在构造任何实例和调用任何静态方法之前。

  2. 实例初始化块实际上由Java编译器复制到类的每个构造函数中。因此,每次执行实例初始化块中的代码恰好在构造函数中的代码之前。


1
因此,如果我创建了10个 SomeClass 实例,则步骤1和2仅执行一次,直到某些原因导致类被卸载(我所能想到的唯一一件事是重新启动程序,但如果有其他原因,我想知道)。 - Glen Pierce
3
@GlenPierce,这是您要的链接:https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.7。 - Biman Tripathy
子类会继承实例初始化块吗? - ceving
不,继承不适用于初始化块。 - Biman Tripathy
实例初始化块实际上会被 Java 编译器复制到类的每个构造函数中 - 但这并不总是正确的。如果构造函数显式调用另一个构造函数,则不会复制它。 - Prasanna
@Prasanna 这是因为如果一个构造函数在另一个构造函数中被显式调用,它必须是第一条语句吗? - torez233

12

aioobe 给出了很好的答案,我再增加几点。

public class StaticTest extends parent {
    static {
        System.out.println("inside satic block");
    }

    StaticTest() {
        System.out.println("inside constructor of child");
    }

    {
        System.out.println("inside initialization block");
    }

    public static void main(String[] args) {
        new StaticTest();
        new StaticTest();
        System.out.println("inside main");
    }
}

class parent {
    static {
        System.out.println("inside parent Static block");
    }
    {
        System.out.println("inside parent initialisation block");
    }

    parent() {
        System.out.println("inside parent constructor");
    }
}
这会给出结果。
inside parent Static block
inside satic block
inside parent initialisation block
inside parent constructor
inside initialization block
inside constructor of child
inside parent initialisation block
inside parent constructor
inside initialization block
inside constructor of child
inside main

这就像是在说显而易见的事情,但似乎更加清晰。


4

这里批准的示例代码是正确的,但我不同意它。它没有展示发生了什么,我将向您展示一个好的例子,以了解JVM实际工作原理:

package test;

    class A {
        A() {
            print();
        }

        void print() {
            System.out.println("A");
        }
    }

    class B extends A {
        static int staticVariable2 = 123456;
        static int staticVariable;

        static
        {
            System.out.println(staticVariable2);
            System.out.println("Static Initialization block");
            staticVariable = Math.round(3.5f);
        }

        int instanceVariable;

        {
            System.out.println("Initialization block");
            instanceVariable = Math.round(3.5f);
            staticVariable = Math.round(3.5f);
        }

        B() {
            System.out.println("Constructor");
        }

        public static void main(String[] args) {
            A a = new B();
            a.print();
            System.out.println("main");
        }

        void print() {
            System.out.println(instanceVariable);
        }

        static void somethingElse() {
            System.out.println("Static method");
        }
    }

在开始对源代码进行评论之前,我将为您简要解释一下类的静态变量:
首先,它们被称为类变量,它们属于类而不是类的特定实例。类的所有实例共享这个静态(类)变量。每个变量都有一个默认值,取决于原始类型或引用类型。另一件事是当您在类的某些成员中重新分配静态变量时(初始化块、构造函数、方法、属性),这样做会改变静态变量的值,而不是特定实例的值,您正在为所有实例更改它。总之,类的静态部分是这样说的:类的静态变量不是在您第一次实例化类时创建的,它们是在定义类时创建的,在JVM中存在,不需要任何实例。因此,从外部类(其中未定义它们的类)正确访问静态成员的方法是使用类名后跟点,然后是要访问的静态成员(模板:<CLASS_NAME>.<STATIC_VARIABLE_NAME>)。
现在让我们看看上面的代码:
入口点是主方法-只有三行代码。我想参考当前已批准的示例。根据它,在打印“Static Initialization block”后必须打印的第一件事是“Initialization block”,这里是我的异议,非静态初始化块不会在构造函数之前调用,它是在定义初始化块的类的任何构造函数初始化之前调用的。当您创建对象(类的实例)并进入构造函数时,类的构造函数是涉及的第一件事,然后是隐式(默认)超级构造函数或显式超级构造函数或对另一个重载构造函数的显式调用(但如果存在一系列重载构造函数,则最后一个构造函数隐式或显式地调用超级构造函数)。
有一个对象的多态创建,但在进入B类及其主方法之前,JVM初始化所有类(静态)变量,然后通过静态初始化块(如果存在)再进入B类并开始执行主方法。它进入B类的构造函数,然后立即(隐式地)调用A类的构造函数,在多态性下,在类A的构造函数体中调用的方法(覆盖的方法)是在类B中定义的方法,在这种情况下,使用了名为instanceVariable的变量进行重新初始化之前。在关闭类B的构造函数后,线程将返回到类B的构造函数,但它首先进入非静态初始化块,然后打印“Constructor”。为了更好地理解,请使用一些IDE进行调试,我更喜欢Eclipse。

10
OP仅仅是询问初始化块的解释,而不是一长串关于静态变量、构造函数或者你对IDE的偏好的解释。简而言之:TL;DR。 - arkon
有时候,这些冗长的解释会意外地变得非常受欢迎。这可能是因为那些提出原始问题的人确实需要详细的解释来打好基础。或者是因为人们单独阅读答案,就像它是某个特定主题的博客一样。但在这种情况下,我想说,它既不是前者也不是后者。 - user7610
@nenito,我认为你对被接受的答案的评论是误导性的。我鼓励你重新表达一下,比如说“我有一个更加细致的解释,可能会引起兴趣。”被接受的答案似乎是完全正确的,只是没有像你的那样详细。 - Glen Pierce
1
@Glen Pierce:在我的评论后,被采纳的答案进行了修改。我的句子不仅提供了答案,还为初级和中级Java开发人员提供了一些额外的有用信息。 - nenito

2

初始化块包含在创建实例时始终执行的代码。它用于声明/初始化类的各种构造函数的公共部分。

初始化构造函数和初始化块的顺序无关紧要,初始化块总是在构造函数之前执行。

如果我们想为类的所有对象执行一次某些代码怎么办?

我们在Java中使用静态块。


1
除了之前的回答,块可以进行同步操作,我从未感到需要使用它,但是它确实存在。

0
public class StaticInitializationBlock {

    static int staticVariable;
    int instanceVariable;

    // Static Initialization Block
    static { 
        System.out.println("Static block");
        staticVariable = 5;

    }

    // Instance Initialization Block
    { 

        instanceVariable = 7;
        System.out.println("Instance Block");
        System.out.println(staticVariable);
        System.out.println(instanceVariable);

        staticVariable = 10;
    }


    public StaticInitializationBlock() { 

        System.out.println("Constructor");
    }

    public static void main(String[] args) {
        new StaticInitializationBlock();
        new StaticInitializationBlock();
    }

}

输出:

Static block
Instance Block
5
7
Constructor
Instance Block
10
7
Constructor

0
问题并不十分清晰,但这里是一些初始化对象数据的简要描述。假设您有一个类A,它保存了一个对象列表。
1)在字段声明中放置初始值:
class A {
    private List<Object> data = new ArrayList<Object>();
}

2) 在构造函数中分配初始值:

class A {
    private List<Object> data;
    public A() {
        data = new ArrayList<Object>();
    }
}

这两个假设您不想将“数据”作为构造函数参数传递。

如果将重载构造函数与上面的内部数据混合使用,情况会变得有点棘手。请考虑:

class B {
    private List<Object> data;
    private String name;
    private String userFriendlyName;

    public B() {
        data = new ArrayList<Object>();
        name = "Default name";
        userFriendlyName = "Default user friendly name";
    }

    public B(String name) {
        data = new ArrayList<Object>();
        this.name = name;
        userFriendlyName = name;
    }

    public B(String name, String userFriendlyName) {
        data = new ArrayList<Object>();
        this.name = name;
        this.userFriendlyName = userFriendlyName;
    }
}

注意到有很多重复的代码。你可以通过让构造函数相互调用来解决这个问题,或者你可以有一个私有的初始化方法,每个构造函数都调用它:
class B {
    private List<Object> data;
    private String name;
    private String userFriendlyName;

    public B() {
        this("Default name", "Default user friendly name");
    }

    public B(String name) {
        this(name, name);
    }

    public B(String name, String userFriendlyName) {
        data = new ArrayList<Object>();
        this.name = name;
        this.userFriendlyName = userFriendlyName;
    }
}

或者

class B {
    private List<Object> data;
    private String name;
    private String userFriendlyName;

    public B() {
        init("Default name", "Default user friendly name");
    }

    public B(String name) {
        init(name, name);
    }

    public B(String name, String userFriendlyName) {
        init(name, userFriendlyName);
    }

    private void init(String _name, String _userFriendlyName) {
        data = new ArrayList<Object>();
        this.name = name;
        this.userFriendlyName = userFriendlyName;
    }
}

这两者(或多或少)是等价的。

希望这能给你一些关于如何在对象中初始化数据的提示。我不会谈论静态初始化块,因为那可能对你来说有点高级。

编辑:我理解你的问题是“如何初始化我的实例变量”,而不是“初始化器块是如何工作的”,因为初始化器块是一个相对高级的概念,从问题的语气来看,似乎你在问一个更简单的概念。我可能错了。


1
即使您将问题解释为“如何初始化我的实例变量?”,您的答案也没有提到可以使用初始化器来完成。 - Thomas Weller

0

初始化块在类被初始化并在构造函数被调用之前执行。它们通常放置在大括号内的构造函数上方。在您的类中包含它们并不是必需的。

它们通常用于初始化引用变量。这个页面提供了一个很好的解释。


1
根据@Biman的说法,超类的构造函数在init块之前运行。 - Nicolas Barbulesco

0

补充一下@aioobe@Biman Tripathy的优秀答案。

静态初始化程序是静态上下文中构造函数的等效物,用于设置静态环境。 实例初始化程序最适合匿名内部类。

  • 在类中可以有多个初始化程序块
  • 当我们有多个初始化程序块时,它们按照出现的顺序执行(实际上由JVM复制到构造函数中)
  • 初始化程序块的顺序很重要,但与构造函数混合的初始化程序块的顺序并不重要
  • 抽象类也可以同时具有静态和实例初始化程序块。

代码演示 -

abstract class Aircraft {

    protected Integer seatCapacity;

    {   // Initial block 1, Before Constructor
        System.out.println("Executing: Initial Block 1");
    }

    Aircraft() {
        System.out.println("Executing: Aircraft constructor");
    }

    {   // Initial block 2, After Constructor
        System.out.println("Executing: Initial Block 2");
    }

}

class SupersonicAircraft extends Aircraft {

    {   // Initial block 3, Internalizing a instance variable
        seatCapacity = 300;
        System.out.println("Executing: Initial Block 3");
    }

    {   // Initial block 4
        System.out.println("Executing: Initial Block 4");
    }

    SupersonicAircraft() {
        System.out.println("Executing: SupersonicAircraft constructor");
    }
}

SupersonicAircraft 的实例创建将按以下顺序生成日志

Executing: Initial Block 1
Executing: Initial Block 2
Executing: Aircraft constructor
Executing: Initial Block 3
Executing: Initial Block 4
Executing: SupersonicAircraft constructor
Seat Capacity - 300

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