CodeGym /课程 /JAVA 25 SELF /对象初始化:初始化顺序

对象初始化:初始化顺序

JAVA 25 SELF
第 14 级 , 课程 5
可用

1. Java 中对象的初始化顺序:谁先,谁后?

在 Java 中创建对象并不只是“分配内存并把值写进去”。这是一个有严格步骤的“仪式”。如果把初始化过程比作火箭发射,那么每一级都有其精确的位置。

当你写出:

Person bob = new Person("Bob", 30);

Java 会执行以下步骤:

  1. 为对象分配内存,并为所有字段赋予默认值。
  2. 执行字段的显式初始化(如果你在声明时直接赋了值)。
  3. 执行所有实例(非静态)初始化块——按照它们在类中书写的顺序。
  4. 执行你通过 new 调用的构造函数的函数体。

下面逐步展开。

2. 字段初始化

默认值

当对象刚被创建(在你显式赋值之前),它的所有字段都会获得默认值:

字段类型 默认值
int, short, byte, long
0
float, double
0.0
boolean
false
char
'\u0000'(空字符)
引用类型(String、其他对象)
null

显式初始化

如果你在声明字段时就赋值,这个值会在默认值之后、但在构造函数之前被设置。

public class Person {
    private String name = "未命名";
    private int age = 18;
}

如果你通过无参构造创建对象,这些值会保留;如果通过带参数的构造函数——它们很可能会被覆盖。

3. 初始化块:用途与工作方式

在 Java 中可以声明所谓的实例(非静态)初始化块。它们在每次创建对象时执行,发生在字段显式初始化之后,但早于构造函数。

语法:

public class Person {
    {
        System.out.println("正在执行实例初始化块!");
    }
}

如果类中有多个这样的块,它们会按书写顺序执行。

多个块的示例

public class Person {
    private String name = "未命名";

    {
        System.out.println("块 1:name = " + name);
        name = "谜";
    }

    {
        System.out.println("块 2:name = " + name);
    }

    public Person() {
        System.out.println("构造函数:name = " + name);
    }
}

如果创建对象:

Person p = new Person();

输出为:

块 1:name = 未命名
块 2:name = 谜
构造函数:name = 谜

初始化块有什么用?

在实际开发中很少用。通常能在块里做的事,要么可以在字段声明处完成,要么可以放在构造函数里。但有时(例如当你有多个构造函数,而部分初始化逻辑需要在所有构造函数中共享)初始化块会很方便。

4. 构造函数:初始化的最后一棒

当所有字段都拿到值(默认或显式),并且所有初始化块都执行完之后,会调用构造函数——也就是你在 new 之后指定的那个。

在构造函数中,你通常会设置字段的最终值、接收参数,并执行依赖输入数据的其他初始化。

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        System.out.println("构造函数:name = " + name + ", age = " + age);
        this.name = name;
        this.age = age;
    }
}

5. 完整初始化顺序演示

让我们写一个类,清楚展示初始化发生的顺序。

public class Person {
    private String name = "未命名";
    private int age = 18;

    {
        System.out.println("初始化块:name = " + name + ", age = " + age);
        age = 21;
    }

    public Person() {
        System.out.println("无参数构造函数:name = " + name + ", age = " + age);
    }

    public Person(String name, int age) {
        System.out.println("带参数的构造函数:name = " + name + ", age = " + age);
        this.name = name;
        this.age = age;
    }

    public void printInfo() {
        System.out.println("Person: name = " + name + ", age = " + age);
    }
}

现在在我们的主类中(例如 Main):

public class Main {
    public static void main(String[] args) {
        System.out.println("创建对象 p1:");
        Person p1 = new Person();
        p1.printInfo();

        System.out.println("\n创建对象 p2:");
        Person p2 = new Person("John", 30);
        p2.printInfo();
    }
}

输出大致如下:

创建对象 p1:
初始化块:name = 未命名, age = 18
无参数构造函数:name = 未命名, age = 21
Person: name = 未命名, age = 21

创建对象 p2:
初始化块:name = 未命名, age = 18
带参数的构造函数:name = 未命名, age = 21
Person: name = John, age = 30

请注意:在带参数的构造函数中,字段值仍然是旧的(即初始化块之后的那些);而在构造函数结束之后,字段才会得到最终的值。

6. 初始化顺序图

这里有一张小的(流程图)用于可视化:

flowchart TD
    A[分配内存,赋默认值] --> B[字段的显式初始化]
    B --> C[执行实例初始化块]
    C --> D[执行构造函数]
    D --> E[对象已可使用]

7. 特点与细节

静态字段和静态块

本文只讨论实例(非静态)字段和块。静态字段和静态块在类加载时只初始化一次,而不是每次创建对象时。关于它们的更多内容,我们会在与封装相关的主题中讨论。

什么时候会初始化?

  • 字段——每次创建对象时。
  • 初始化块——每次创建对象时。
  • 静态字段和静态块——仅在类加载时初始化一次。

能否在初始化块中访问字段?

可以!但要注意,如果字段声明在该块之后,它已经存在;而如果你在它的显式初始化之前访问它,你会得到默认值。

8. 对象初始化中的常见错误

错误 №1:以为字段会在声明之前就被初始化。
在 Java 中,类中字段和初始化块的声明顺序是有意义的。如果你在初始化块中访问一个声明在其下方的字段,它虽然已经存在,但还没有被显式初始化——得到的将是默认值。

错误 №2:在块和构造函数中重复初始化。
新手常常在块和构造函数里重复相同的逻辑。更好的做法是:要么用初始化块,要么用构造函数,或者通过 this(...) 让一个构造函数调用另一个构造函数。

错误 №3:以为静态字段会在每次创建对象时初始化。
静态字段和块的工作方式不同——它们只会在类加载时初始化一次。

错误 №4:使用未初始化的引用字段。
如果你忘了显式初始化某个引用字段(例如,String name;),那么在你在初始化块或构造函数中显式赋值之前,它的值将是 null

1
调查/小测验
类和构造器第 14 级,课程 5
不可用
类和构造器
类和构造器
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION