📃个人主页:小韩学长yyds-CSDN博客
⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏 💞 💞 💞
箴言:拥有耐心才是生活的关键
目录
介绍
Java 类初始化顺序理论知识
静态变量和静态初始化块
实例变量和非静态初始化块
构造函数
静态方法
代码示例与分析
示例代码展示
代码执行结果分析
有继承关系的类初始化顺序
继承关系下的初始化规则
示例代码及分析
绘制流程图
无继承关系类的初始化流程图
有继承关系类的初始化流程图
介绍
在 Java 编程中,类的初始化顺序是一个基础且重要的知识点。理解类的初始化顺序,有助于我们更好地掌握 Java 的运行机制,编写出更健壮、更高效的代码。无论是在日常开发中,还是在面试过程中,类的初始化顺序相关问题都经常出现。比如,在涉及到复杂的继承关系和静态资源管理时,如果对初始化顺序不了解,可能会导致程序出现意想不到的错误。接下来,让我们深入探讨 Java 中类的初始化顺序,揭开静态方法、静态块、非静态块在其中扮演的角色。
Java 类初始化顺序理论知识
静态变量和静态初始化块
当类被加载到 JVM 时(即第一次主动使用该类时,如创建类的实例、访问类的静态变量或静态方法等),类的静态变量和静态初始化块会被初始化。静态初始化块(也称为静态代码块)在静态变量初始化之后、创建类的任何实例之前执行。如果有多个静态初始化块,它们会按照在源代码中出现的顺序执行。静态变量和静态初始化块只会在类被加载时执行一次 。
例如,以下代码展示了静态变量和静态初始化块的执行顺序:
public class StaticInitExample {
// 静态变量
private static int staticVar = initializeStaticVar();
// 静态初始化块
static {
System.out.println("静态初始化块执行");
}
// 静态方法,用于初始化静态变量
public static int initializeStaticVar() {
System.out.println("静态方法initializeStaticVar执行");
return 1;
}
public static void main(String[] args) {
// 第一次主动使用类,触发类的加载和静态初始化
System.out.println("静态变量staticVar的值:" + staticVar);
}
}
运行上述代码,输出结果为:
静态方法initializeStaticVar执行
静态初始化块执行
静态变量staticVar的值:1
可以看到,静态方法initializeStaticVar先执行,为静态变量staticVar赋值,然后执行静态初始化块,最后在main方法中打印静态变量的值。
实例变量和非静态初始化块
当创建类的实例时,实例变量和非静态初始化块(也称为实例初始化块或初始化块)会被初始化。如果一个类有多个非静态初始化块,它们会按照在源代码中出现的顺序执行。非静态初始化块在实例变量初始化之后、类的构造函数之前执行。每次创建类的实例时,都会执行非静态初始化块。
例如:
public class InstanceInitExample {
// 实例变量
private int instanceVar;
// 非静态初始化块
{
System.out.println("非静态初始化块执行");
instanceVar = 42;
}
// 构造函数
public InstanceInitExample() {
System.out.println("构造函数执行");
}
public static void main(String[] args) {
// 创建类的实例,触发实例初始化和构造函数
InstanceInitExample example = new InstanceInitExample();
}
}
运行结果为:
非静态初始化块执行
构造函数执行
从结果可知,创建实例时,先执行非静态初始化块,再执行构造函数。
构造函数
在非静态初始化块执行完毕后,会调用类的构造函数。构造函数用于初始化实例的状态,并在每次创建类的实例时执行。如果有多个构造函数,可以根据传入的参数来选择使用哪一个。如果在类的定义中没有显式地提供构造函数,编译器会提供一个默认的构造函数。
构造函数的主要作用是完成对类对象的初始化工作,它的方法名与类名相同,并且没有返回类型。比如在以下代码中:
public class ConstructorExample {
private int value;
// 构造函数
public ConstructorExample(int v) {
value = v;
System.out.println("构造函数初始化value为:" + value);
}
public static void main(String[] args) {
ConstructorExample example = new ConstructorExample(10);
}
}
运行结果为:
构造函数初始化value为:10
这里通过构造函数,在创建对象时对value进行了初始化。
静态方法
静态方法属于类本身,而不是类的实例。静态方法可以通过类名直接调用,无需创建类的实例。静态方法不能访问类的非静态成员(包括非静态变量和非静态方法),除非通过类的实例来访问。静态方法的执行不依赖于类的初始化过程。它们可以在类被加载之前、之后或类的实例被创建之前、之后调用。
比如:
public class StaticMethodExample {
private static int staticVar = 10;
private int instanceVar = 20;
// 静态方法
public static void staticMethod() {
System.out.println("静态方法执行,静态变量staticVar的值为:" + staticVar);
// 以下代码会报错,因为静态方法不能直接访问非静态变量
// System.out.println("非静态变量instanceVar的值为:" + instanceVar);
}
public void instanceMethod() {
System.out.println("实例方法执行,静态变量staticVar的值为:" + staticVar);
System.out.println("实例方法执行,非静态变量instanceVar的值为:" + instanceVar);
}
public static void main(String[] args) {
// 直接调用静态方法,无需创建实例
StaticMethodExample.staticMethod();
StaticMethodExample example = new StaticMethodExample();
example.instanceMethod();
}
}
运行结果为:
静态方法执行,静态变量staticVar的值为:10
实例方法执行,静态变量staticVar的值为:10
实例方法执行,非静态变量instanceVar的值为:20
可以看到,静态方法staticMethod可以直接通过类名调用,并且只能访问静态变量;而实例方法instanceMethod需要通过对象实例调用,能访问静态变量和非静态变量。
代码示例与分析
示例代码展示
为了更直观地理解 Java 中类的初始化顺序,我们来看一个综合的代码示例:
public class InitializationOrderExample {
// 静态变量
private static int staticVar = 10;
// 静态初始化块
static {
System.out.println("静态初始化块,静态变量staticVar的值:" + staticVar);
staticVar = 20;
System.out.println("静态初始化块修改后,静态变量staticVar的值:" + staticVar);
}
// 实例变量
private int instanceVar = 30;
// 非静态初始化块
{
System.out.println("非静态初始化块,实例变量instanceVar的值:" + instanceVar);
instanceVar = 40;
System.out.println("非静态初始化块修改后,实例变量instanceVar的值:" + instanceVar);
}
// 构造函数
public InitializationOrderExample() {
System.out.println("构造函数,实例变量instanceVar的值:" + instanceVar);
instanceVar = 50;
System.out.println("构造函数修改后,实例变量instanceVar的值:" + instanceVar);
}
// 静态方法
public static void staticMethod() {
System.out.println("静态方法执行,静态变量staticVar的值:" + staticVar);
}
public static void main(String[] args) {
// 调用静态方法,触发类的加载和静态初始化
InitializationOrderExample.staticMethod();
// 创建类的实例,触发实例初始化和构造函数
InitializationOrderExample example = new InitializationOrderExample();
}
}
代码执行结果分析
静态部分初始化:当程序执行到InitializationOrderExample.staticMethod()时,类InitializationOrderExample被加载到 JVM 中,开始静态初始化。首先,静态变量staticVar被初始化为 10,然后执行静态初始化块。在静态初始化块中,先打印出staticVar的值为 10,接着将其修改为 20,并再次打印。此时,静态初始化完成,staticVar的值为 20 。实例部分初始化:当执行new InitializationOrderExample()时,开始实例初始化。首先,实例变量instanceVar被初始化为 30,然后执行非静态初始化块。在非静态初始化块中,先打印出instanceVar的值为 30,接着将其修改为 40,并再次打印。最后,执行构造函数。在构造函数中,先打印出instanceVar的值为 40,接着将其修改为 50,并再次打印。
完整的输出结果如下:
静态方法执行,静态变量staticVar的值:20
静态初始化块,静态变量staticVar的值:10
静态初始化块修改后,静态变量staticVar的值:20
非静态初始化块,实例变量instanceVar的值:30
非静态初始化块修改后,实例变量instanceVar的值:40
构造函数,实例变量instanceVar的值:40
构造函数修改后,实例变量instanceVar的值:50
通过这个示例和分析,我们可以清晰地看到 Java 类中静态变量、静态初始化块、实例变量、非静态初始化块和构造函数的初始化顺序,以及它们之间的相互作用 。这对于我们编写正确、高效的 Java 代码非常重要,尤其是在处理复杂的类结构和对象初始化逻辑时。
有继承关系的类初始化顺序
继承关系下的初始化规则
在 Java 中,当存在继承关系时,类的初始化顺序变得更加复杂,但也遵循严格的规则。首先,父类的静态变量和静态初始化块会被初始化,这是因为静态成员属于类本身,在类被加载时就需要准备好。而且静态成员只会在类首次加载时初始化一次。接着,子类的静态变量和静态初始化块会被初始化。
当创建子类的实例时,会先初始化父类的非静态变量和非静态初始化块,为父类实例的状态做准备。然后调用父类的构造函数,完成父类实例的初始化。之后,再初始化子类的非静态变量和非静态初始化块,最后调用子类的构造函数 。这个顺序确保了在子类实例化之前,父类已经被正确地初始化,子类可以依赖父类的初始化结果。
示例代码及分析
下面是一个展示有继承关系的类初始化顺序的示例代码:
class ParentClass {
// 父类静态变量
private static int parentStaticVar = 100;
// 父类静态初始化块
static {
System.out.println("父类静态初始化块,父类静态变量parentStaticVar的值:" + parentStaticVar);
parentStaticVar = 200;
System.out.println("父类静态初始化块修改后,父类静态变量parentStaticVar的值:" + parentStaticVar);
}
// 父类实例变量
private int parentInstanceVar = 300;
// 父类非静态初始化块
{
System.out.println("父类非静态初始化块,父类实例变量parentInstanceVar的值:" + parentInstanceVar);
parentInstanceVar = 400;
System.out.println("父类非静态初始化块修改后,父类实例变量parentInstanceVar的值:" + parentInstanceVar);
}
// 父类构造函数
public ParentClass() {
System.out.println("父类构造函数,父类实例变量parentInstanceVar的值:" + parentInstanceVar);
parentInstanceVar = 500;
System.out.println("父类构造函数修改后,父类实例变量parentInstanceVar的值:" + parentInstanceVar);
}
}
class ChildClass extends ParentClass {
// 子类静态变量
private static int childStaticVar = 600;
// 子类静态初始化块
static {
System.out.println("子类静态初始化块,子类静态变量childStaticVar的值:" + childStaticVar);
childStaticVar = 700;
System.out.println("子类静态初始化块修改后,子类静态变量childStaticVar的值:" + childStaticVar);
}
// 子类实例变量
private int childInstanceVar = 800;
// 子类非静态初始化块
{
System.out.println("子类非静态初始化块,子类实例变量childInstanceVar的值:" + childInstanceVar);
childInstanceVar = 900;
System.out.println("子类非静态初始化块修改后,子类实例变量childInstanceVar的值:" + childInstanceVar);
}
// 子类构造函数
public ChildClass() {
System.out.println("子类构造函数,子类实例变量childInstanceVar的值:" + childInstanceVar);
childInstanceVar = 1000;
System.out.println("子类构造函数修改后,子类实例变量childInstanceVar的值:" + childInstanceVar);
}
}
public class InheritanceInitializationExample {
public static void main(String[] args) {
// 创建子类实例,触发类的加载和初始化
ChildClass child = new ChildClass();
}
}
静态部分初始化:当程序执行到new ChildClass()时,ChildClass类被加载到 JVM 中,由于它继承自ParentClass,所以先加载ParentClass。ParentClass的静态变量parentStaticVar被初始化为 100,然后执行其静态初始化块,在块中先打印parentStaticVar的值为 100,再修改为 200 并打印。接着,ChildClass的静态变量childStaticVar被初始化为 600,执行其静态初始化块,先打印childStaticVar的值为 600,再修改为 700 并打印。实例部分初始化:开始创建ChildClass的实例,先初始化ParentClass的实例部分。ParentClass的实例变量parentInstanceVar被初始化为 300,执行其非静态初始化块,先打印parentInstanceVar的值为 300,再修改为 400 并打印。然后调用ParentClass的构造函数,先打印parentInstanceVar的值为 400,再修改为 500 并打印。接着,初始化ChildClass的实例部分,ChildClass的实例变量childInstanceVar被初始化为 800,执行其非静态初始化块,先打印childInstanceVar的值为 800,再修改为 900 并打印。最后,调用ChildClass的构造函数,先打印childInstanceVar的值为 900,再修改为 1000 并打印。
完整的输出结果如下:
父类静态初始化块,父类静态变量parentStaticVar的值:100
父类静态初始化块修改后,父类静态变量parentStaticVar的值:200
子类静态初始化块,子类静态变量childStaticVar的值:600
子类静态初始化块修改后,子类静态变量childStaticVar的值:700
父类非静态初始化块,父类实例变量parentInstanceVar的值:300
父类非静态初始化块修改后,父类实例变量parentInstanceVar的值:400
父类构造函数,父类实例变量parentInstanceVar的值:400
父类构造函数修改后,父类实例变量parentInstanceVar的值:500
子类非静态初始化块,子类实例变量childInstanceVar的值:800
子类非静态初始化块修改后,子类实例变量childInstanceVar的值:900
子类构造函数,子类实例变量childInstanceVar的值:900
子类构造函数修改后,子类实例变量childInstanceVar的值:1000
通过这个示例和分析,我们可以清晰地看到在继承关系下,Java 类的静态部分和非静态部分是如何按照顺序进行初始化的 。这对于理解 Java 的面向对象特性和编写正确的代码非常重要,特别是在处理复杂的继承体系时,能帮助我们避免很多潜在的错误。
绘制流程图
无继承关系类的初始化流程图
为了更直观地展示无继承关系类的初始化顺序,我们可以绘制如下流程图:
在这个流程图中,首先类被加载到 JVM,然后静态变量被初始化,接着执行静态初始化块。如果创建类的实例,那么会初始化实例变量,执行非静态初始化块,最后调用构造函数。如果不创建实例,流程结束 。
有继承关系类的初始化流程图
对于有继承关系的类,初始化顺序的流程图如下:
在有继承关系的情况下,先加载父类到 JVM,完成父类静态变量的初始化和静态初始化块的执行,然后加载子类到 JVM,进行子类静态部分的初始化。当创建子类实例时,先初始化父类的实例部分,调用父类构造函数,再初始化子类的实例部分,最后调用子类构造函数 。如果不创建子类实例,流程结束。通过这两个流程图,我们可以更清晰地理解 Java 中类的初始化顺序,无论是在无继承关系还是有继承关系的情况下。
结语
🔥如果此文对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏、✍️评论,支持一下博主~