【Java】static关键字与类的加载顺序

static关键字

核心含义

static译为 "静态的",在 Java 中它的核心作用是:将类的成员(变量、方法、代码块、内部类)与类本身绑定,而非与类的实例(对象)绑定

简单来说:

  • 非静态成员:属于 "对象",每个对象有独立的一份,必须通过对象访问。
  • 静态成员:属于 "类",整个程序运行期间只有一份副本,可直接通过类名访问,无需创建对象。

具体用法

静态变量(类变量)

被 static 修饰的变量称为类变量,归属类本身而非类的具体对象实例 ,由该类所有实例化的对象共享同一份内存副本,任意一个对象修改该值,所有对象访问到的都会是修改后的值。

静态变量在 JVM 加载类时(类加载阶段)完成初始化,存储于方法区,整个程序运行期间仅初始化一次,生命周期与类的生命周期绑定;可通过「类名.变量名」直接访问。

特点

  • static 修饰的成员变量,称为静态变量 / 类变量。
  • 存储在方法区(而非堆内存),类加载时初始化,生命周期与类一致。
  • 所有对象共享同一份静态变量,修改一个对象的静态变量,会影响所有对象。
代码示例
java 复制代码
public class Student {
    // 非静态变量(实例变量):每个学生对象有独立的名字
    private String name;
    // 静态变量(类变量):所有学生共享同一个学校名称
    public static String school = "北京大学";

    // 构造方法
    public Student(String name) {
        this.name = name;
    }

    // 获取名字
    public String getName() {
        return name;
    }

    public static void main(String[] args) {
        // 1. 直接通过类名访问静态变量(推荐)
        System.out.println(Student.school); // 输出:北京大学

        // 2. 创建对象访问(不推荐,易混淆)
        Student s1 = new Student("张三");
        Student s2 = new Student("李四");
        System.out.println(s1.school); // 输出:北京大学
        System.out.println(s2.school); // 输出:北京大学

        // 3. 修改静态变量(所有对象都会受影响)
        s1.school = "清华大学";
        System.out.println(s2.school); // 输出:清华大学
        System.out.println(Student.school); // 输出:清华大学
    }
}

上述代码中name为非静态变量,对象s1、s2创建后在堆内存当中进行存储,且是对象私有的;

school被static修饰,是静态变量,存储在方法区中,我们在main方法中给school重新赋值为清华大学,所有对象的school变量都发生了改变。

常见使用场景
  • 定义常量(配合 finalpublic static final double PI = 3.1415926;)。
  • 统计类的实例数量(如 private static int count = 0;,构造方法中 count++)。
静态方法(类方法)

被 static 关键字修饰的方法被规范称为静态方法(Static Method),也常称作类方法(Class Method),其核心本质是归属类的全局范畴,而非类实例化后的具体对象实例,是类的固有行为,不依赖任何对象的状态(属性)。

特点

  • static 修饰的方法,称为静态方法 / 类方法。
  • 静态方法属于类,可直接通过类名调用,无需创建对象。
  • 核心限制 :静态方法中不能访问非静态成员(非静态变量、非静态方法),也不能使用 this/super 关键字(因为 this 指向当前对象,而静态方法不依赖对象)。
代码示例
java 复制代码
public class MathUtil {
    // 静态方法:计算两数之和(工具类方法常用static)
    public static int add(int a, int b) {
        // 静态方法中不能访问非静态变量(如下行会报错)
        // System.out.println(nonStaticVar);
        return a + b;
    }

    // 非静态方法
    public void printInfo() {
        System.out.println("这是非静态方法");
        // 非静态方法可以访问静态方法/变量(合法)
        System.out.println(add(1, 2));
    }

    public static void main(String[] args) {
        // 1. 直接通过类名调用静态方法(推荐)
        int sum = MathUtil.add(3, 5);
        System.out.println(sum); // 输出:8

        // 2. 通过对象调用(不推荐)
        MathUtil util = new MathUtil();
        sum = util.add(4, 6);
        System.out.println(sum); // 输出:10

        // 3. 调用非静态方法(必须创建对象)
        util.printInfo();
    }
}

注意:在同一个类中访问本类的静态成员(方法 / 变量),可以省略 "类名." 的前缀

所以System.out.println(add(1, 2));也能正常输出3

常见使用场景
  • 工具类(如 java.lang.Mathjava.util.Arrays 中的所有方法都是静态的)。
  • 工厂方法(创建对象的方法,如 Integer.valueOf())。
  • 主方法 main()(必须是 static,因为程序启动时还没有创建类的对象)。
静态代码块

被 static 关键字修饰的代码块称为静态代码块(Static Block),也叫静态初始化块,是 Java 中专门用于初始化类级静态成员(静态变量、静态资源)的特殊代码结构,其核心设计目的是为静态成员提供复杂的初始化逻辑。

特点

  • static {} 包裹的代码块,称为静态代码块。
  • 作用:初始化静态变量,或执行类加载时需要的一次性操作。
  • 执行时机:类加载时(早于构造方法)执行,且只执行一次(无论创建多少对象,静态代码块仅执行一次)。
代码示例
java 复制代码
public class StaticBlockDemo {
    // 静态变量
    public static int num;

    // 静态代码块:初始化静态变量
    static {
        System.out.println("静态代码块执行");
        num = 100;
        // 可以执行复杂的初始化逻辑,如读取配置文件
    }

    // 构造方法
    public StaticBlockDemo() {
        System.out.println("构造方法执行");
    }

    // 构造代码块
    {
        System.out.println("构造代码块执行");
    }

    public static void main(String[] args) {
        // 创建第一个对象:先执行静态代码块,再执行构造方法
        StaticBlockDemo demo1 = new StaticBlockDemo();
        System.out.println(demo1.num); // 输出:100

        // 创建第二个对象:静态代码块不再执行,仅执行构造方法
        StaticBlockDemo demo2 = new StaticBlockDemo();
        System.out.println(demo2.num); // 输出:100
    }
}
执行顺序

类加载时的执行顺序:静态变量声明/赋值静态代码块 →main方法执行 →构造代码块({}) → 构造方法。

  • main 方法本身是静态方法,属于类级别的成员,因此必须等类加载完成(静态代码块执行完毕)后才能执行;
  • 构造代码块和构造方法是对象级别的 ,只有在 main 方法中执行 new 类名() 创建对象时才会触发,因此必然在 main 方法执行过程中(且在创建对象的那一行)才会执行,且晚于 main 方法的启动。
  • 从输出结果可以看到,静态代码块只执行了一次,而构造代码块和构造方法每次对象创建都会执行
静态内部类
定义与特点
  • static 修饰的内部类,称为静态内部类(也叫嵌套类)。
  • 特点:
    1. 静态内部类属于外部类,而非外部类的对象。
    2. 可以直接创建静态内部类的对象,无需先创建外部类对象。
    3. 静态内部类中可以访问外部类的静态成员,但不能直接访问外部类的非静态成员(如需访问,需传入外部类对象)。
代码示例
java 复制代码
public class OuterClass {
    // 外部类静态变量
    public static String outerStaticVar = "外部类静态变量";
    // 外部类非静态变量
    public String outerNonStaticVar = "外部类非静态变量";

    // 静态内部类
    public static class StaticInnerClass {
        public void print() {
            // 可以直接访问外部类的静态变量
            System.out.println(outerStaticVar);

            // 不能直接访问外部类的非静态变量(如下行会报错)
            // System.out.println(outerNonStaticVar);

            // 如需访问,需创建外部类对象
            OuterClass outer = new OuterClass();
            System.out.println(outer.outerNonStaticVar);
        }
    }

    public static void main(String[] args) {
        // 创建静态内部类对象(无需先创建外部类对象)
        OuterClass.StaticInnerClass inner = new OuterClass.StaticInnerClass();
        inner.print();
    }
}

static 的常见误区

  1. 误区 1 :认为静态成员可以被所有类访问。→ 错误:静态成员的访问权限仍受 public/private/protected 修饰符限制(如 private static 变量仅能在本类访问)。
  2. 误区 2:静态方法中可以重写(Override)。→ 错误:静态方法属于类,不存在 "重写"(重写是针对对象的多态特性),子类的静态方法只是 "隐藏" 了父类的静态方法,而非重写。
  3. 误区 3 :静态变量线程安全。→ 错误:静态变量被所有线程共享,多线程环境下直接修改静态变量会导致线程安全问题(需加锁,如 synchronized)。

总结

  1. static 的核心是绑定到类,而非对象:静态成员属于类,仅一份副本,可通过类名直接访问;非静态成员属于对象,每个对象一份。
  2. 静态方法的核心限制:不能访问非静态成员,不能用 this/super
  3. 常见使用场景:工具类方法、常量定义、类加载时的初始化(静态代码块)、静态内部类(简化类结构)。

掌握 static 的关键是理解 "类级别的成员" 和 "对象级别的成员" 的区别,在实际开发中,合理使用 static 可以减少对象创建、提升性能,但过度使用(如滥用静态变量存储状态)会导致代码耦合度高、线程安全问题,需谨慎。

类的加载顺序

我们来看下面的代码:

java 复制代码
// 父类
class Parent {
    // 父类静态变量1
    static int parentStaticVar = printNum("1.父类静态变量显式赋值");
    
    // 父类静态代码块1
    static {
        printNum("2.父类静态代码块1");
    }
    
    // 父类静态代码块2
    static {
        printNum("3.父类静态代码块2");
    }

    // 父类实例变量1
    int parentInstanceVar = printNum("5.父类实例变量显式赋值");
    
    // 父类实例代码块
    {
        printNum("6.父类实例代码块");
    }

    // 父类构造方法
    public Parent() {
        printNum("7.父类构造方法");
    }

    // 辅助打印方法(静态)
    public static int printNum(String msg) {
        System.out.println(msg);
        return 0; // 仅为赋值用,无实际意义
    }
}

// 子类
class Child extends Parent {
    // 子类静态变量1
    static int childStaticVar = printNum("4.子类静态变量显式赋值");
    
    // 子类静态代码块
    static {
        printNum("4.子类静态代码块"); // 注意:和静态变量按声明顺序,这里是第4步后
    }

    // 子类实例变量1
    int childInstanceVar = printNum("8.子类实例变量显式赋值");
    
    // 子类实例代码块
    {
        printNum("9.子类实例代码块");
    }

    // 子类构造方法
    public Child() {
        // 隐含super(),会先执行父类实例初始化
        printNum("10.子类构造方法");
    }
}

// 测试类
public class ClassLoadOrderTest {
    public static void main(String[] args) {
        System.out.println("=====第一次创建子类实例=====");
        Child c1 = new Child();

        System.out.println("\n=====第二次创建子类实例=====");
        Child c2 = new Child();
    }
}

输出:

类初始化阶段(仅执行一次)

① 父类静态变量显式赋值 → ② 父类静态代码块(按声明顺序)

→ ③ 子类静态变量显式赋值 → ④ 子类静态代码块(按声明顺序)

对象实例化阶段(每次new子类都执行)

⑤ 父类实例变量显式赋值 → ⑥ 父类实例代码块(按声明顺序)

→ ⑦ 父类构造方法

→ ⑧ 子类实例变量显式赋值 → ⑨ 子类实例代码块(按声明顺序)

→ ⑩ 子类构造方法

JVM 执行 main 方法的完整流程:

定位 main 方法所在的类;

初始化该类(执行其静态变量 / 静态代码块,若有父类则先初始化父类);

执行 main 方法;

main 方法执行过程中,若用到其他类,则初始化该类(执行其静态变量 / 静态代码块)。

所以需要注意的是静态资源属于 main 方法所在类则在 main() 前执行

详细解释:

阶段 1:加载入口类,执行 main 方法开头

JVM 启动后,首先加载 ClassLoadOrderTest 类(包含 main 方法的入口类),由于该类中无其他静态变量 / 静态代码块则直接执行 main 方法,输出第一次创建子类实例;

阶段 2:第一次创建 Child 实例触发类初始化与实例化

执行 new Child() 时,JVM 发现 Child 类未初始化,且 Child 继承自 Parent,因此先完成父类→子类的静态初始化,再执行父类→子类的实例化(静态资源是类级别的,父类是子类的基础,因此先初始化父类静态资源)。

父类 Parent 的静态初始化(仅执行 1 次):按代码书写顺序执行父类的静态资源(静态变量显式赋值 + 静态代码块);

子类 Child 的静态初始化(仅执行 1 次):父类静态初始化完成后,按代码书写顺序执行子类的静态资源(静态变量显式赋值 + 静态代码块);

父类 Parent 的实例化(子类构造隐含 super ()):子类静态初始化完成后,开始实例化 Child 对象,子类构造方法第一行隐含super()(调用父类无参构造),因此先执行父类的实例资源实例(变量显式赋值、实例代码块),按照代码书写的先后顺序执行;

子类 Child 的实例化:父类实例化完成后,执行子类的实例资源(实例变量显式赋值、实例代码块),按照代码书写的先后顺序执行;

阶段 3:第二次创建 Child 实例仅执行实例化

此时 Parent 和 Child 的静态初始化已完成,因此仅执行父类→子类的实例化。

相关推荐
山峰哥2 小时前
SQL查询优化秘籍:从Explain分析到性能飞跃
开发语言·数据库·sql·oracle·性能优化·系统优化
9号达人2 小时前
支付配置时好时坏?异步方法里的对象引用坑
java·后端·面试
世转神风-2 小时前
qt-通信协议基础-QStirng转QByteArray-与字节序互动
开发语言·qt
资生算法程序员_畅想家_剑魔2 小时前
Java常见技术分享-14-多线程安全-锁机制-常见的锁以及底层实现-synchronized
java·开发语言
江沉晚呤时2 小时前
构建智能代理的利器:深入解析 Microsoft Agent Framework
开发语言·c#
JoStudio2 小时前
白帽系列01: 抓包
java·网络安全
走粥2 小时前
JavaScript Promise
开发语言·前端·javascript
范纹杉想快点毕业2 小时前
C语言设计模式:从基础架构到高级并发系统(完整实现版)
c语言·开发语言·设计模式
sanggou2 小时前
基于Java实现的简易规则引擎(日常开发难点记录)
android·java