Java static 静态变量 / 静态方法 超详细讲解(底层原理 + 内存分配 + 使用场景)

23 阅读16分钟

一、先抛出核心问题:为什么需要 static 关键字?

我们先看 汽车司机类 CarDriver 的例子:

运行

public class CarDriver {
    // 成员变量:每个司机对象 独有 的属性
    private String name; // 每个司机姓名不同
    private int age;     // 每个司机年龄不同
    private String license; // 每个司机驾照号不同
    
    // 思考:所有司机都有一个共同的属性 → 职业名称都是「司机」,这个属性值 所有对象都一样
    // 如果写成成员变量:private String job = "司机";
    // 问题:创建100个司机对象,堆内存中就会存100个"司机"字符串,完全重复,极度浪费内存!
}

👉 核心痛点:当一个属性 / 方法,是属于【类本身】的、所有对象共享的、值完全相同的,如果定义成普通的成员变量 / 方法,会在每个对象中都存一份,造成巨大的内存浪费

static 关键字就是为了解决这个问题而生的:

static 的核心作用:把「成员变量 / 方法」从【对象级】提升为【类级】,成为类的共有资源,所有对象共享同一份数据,内存中只存一份,极大节省内存。


二、核心铺垫:复习 Java 的三大核心内存区域(static专属内存必看)

理解static的内存分配,只需要牢记这三块内存,和上一篇字符串的内存完全一致,只是补充了 static 的专属区域,先复习巩固,重中之重:

✅ 1. 堆内存(Heap)

  • 存储内容:所有通过new关键字创建的【对象本身】 (比如new CarDriver()new MyMath()new StringBuilder())、对象的成员变量(非 static 的变量,比如 name、age);
  • 核心特点:一个对象一份内存,不同对象的成员变量相互独立,互不影响。

✅ 2. 方法区(Method Area,JDK8 叫【元空间】)【static的专属内存,核心核心】

  • 存储内容:① 类的字节码信息(类的结构、方法定义、变量定义,只要类被加载,就会存到这里);② 所有被 static 修饰的内容 → 静态变量、静态方法、静态代码块;③ 字符串常量池(上一篇学的,String 的字面量);
  • 核心特点:① JVM 启动时就加载,内存中永远只有一份,不会随对象的创建而创建,也不会随对象的销毁而销毁;② 只要类存在,static 的内容就存在,所有对象共享同一份 static 数据;③ 生命周期:和类的生命周期一致(类加载时初始化,程序结束时销毁)。

✅ 3. 栈内存(Stack)

  • 存储内容:方法的局部变量、方法的调用栈、对象的引用(变量名);
  • 核心特点:方法执行完,栈中的内容就会被销毁,速度最快。

三、static 静态变量 核心讲解(重点中的重点)

✅ 1. 什么是静态变量?定义格式

✔ 概念

static 关键字修饰的成员变量,称为「静态变量」,也叫「类变量」。与之对应的是:非 static 的成员变量 → 叫「实例变量 / 对象变量」(你之前写的 name、age 都是)。

✔ 定义格式(写在类中,方法外)

java

运行

public class 类名 {
    // 1. 静态变量:加static修饰,推荐加访问修饰符
    public static 数据类型 变量名 = 初始值;
    
    // 2. 实例变量:不加static,你的代码里全是这种
    public 数据类型 变量名 = 初始值;
}

✔ 你的代码改造示例(CarDriver 类,最贴合你的场景)

java

运行

public class CarDriver {
    // ✅ 实例变量:每个对象 独有,堆内存中每个对象存一份
    private String name;
    private int age;
    private String license;
    private String carNum;
    
    // ✅ 静态变量:所有司机对象 共享,方法区中只存一份,值完全相同
    public static String job = "汽车司机"; // 所有司机的职业都是这个,不用每个对象存一份
    
    // get/set方法、toString方法省略
}

✅ 2. 静态变量的【内存分配规则】(核心!必背!内存图 + 文字详解)

这是static最核心的知识点,也是面试必考的内容,我们用上面的CarDriver类举例子,执行如下代码,看内存分配的完整过程:

java

运行

public static void main(String[] args) {
    // 创建第一个司机对象
    CarDriver driver1 = new CarDriver();
    driver1.setName("张三");
    driver1.setAge(30);
    
    // 创建第二个司机对象
    CarDriver driver2 = new CarDriver();
    driver2.setName("李四");
    driver2.setAge(28);
}

✔ 完整内存分配步骤(按执行顺序,一步步看,看懂这个 = 懂了 static 内存)

步骤 1:类加载 → 方法区中初始化 static静态变量

当 JVM 执行到CarDriver类时,会先把类的字节码加载到方法区,同时:

  • 在方法区中,为静态变量 job 分配内存,赋值为 "汽车司机"
  • ✅ 关键:此时还没有创建任何对象,静态变量就已经存在了,内存中只存一份
步骤 2:创建对象 → 堆内存中初始化 实例变量

执行new CarDriver()时,JVM 在堆内存中创建一个司机对象:

  • 为实例变量 name、age、license、carNum 分配内存(默认值:String=null,int=0);
  • 通过 set 方法赋值后,堆中对象的成员变量有了具体值;
  • ✅ 关键:堆中的对象,不会存储静态变量!静态变量只在方法区存一份
步骤 3:创建第二个对象 → 堆内存新增对象,静态变量依然只有一份

执行第二个new CarDriver()时:

  • 堆内存中新增一个独立的对象,有自己的 name、age 等实例变量;
  • 方法区中的静态变量job没有任何变化,还是原来的那一份

✔ 核心内存结论(一句话总结,刻进脑子里)

**静态变量 → 存在【方法区】,内存中永远只存一份,属于类本身,所有对象共享;**实例变量 → 存在【堆内存】,每个对象存一份,属于对象本身,对象之间互不影响。

✔ 直观内存图(文字版,好理解)

plaintext

【栈内存】:
  driver1 → 指向 堆内存的对象1
  driver2 → 指向 堆内存的对象2

【堆内存】:
  对象1:name=张三, age=30, license=null, carNum=null (无静态变量)
  对象2:name=李四, age=28, license=null, carNum=null (无静态变量)

【方法区】:
  CarDriver类的字节码信息
  静态变量:job = "汽车司机"  → 所有对象共享这一份!

四、静态变量 vs 实例变量 核心区别(必背!必考!最全对比表)

这是static核心考点,也是你写代码时判断是否用 static 的唯一依据,所有区别都源于内存分配的不同,整理成你最容易记的对比表,优先级⭐越多越重要:

对比维度静态变量(类变量)static实例变量(对象变量)无 static
所属者✅ 属于【类本身】✅ 属于【对象本身】
内存位置✅ 方法区(元空间)✅ 堆内存
内存份数✅ 内存中永远只存 1 份✅ 每个对象存1 份,创建 n 个对象存 n 份
初始化时机✅ 类加载时就初始化(早)✅ 创建对象时才初始化(晚)
生命周期✅ 和类的生命周期一致(程序结束销毁)✅ 和对象的生命周期一致(对象被 GC 回收就销毁)
调用方式✅ 两种方式:① 类名。静态变量 (推荐,规范)② 对象名。静态变量 (语法允许,不推荐)✅ 只有 1 种方式:对象名。实例变量 (必须创建对象才能调用)
核心特点✅ 所有对象共享同一份数据,一处修改,处处生效✅ 每个对象独有数据,修改一个对象的变量,其他对象不受影响
使用场景✅ 所有对象共用不变的值(比如职业、常量)✅ 统计对象的个数(比如统计创建了多少个司机)✅ 每个对象独有的属性(比如姓名、年龄、驾照号)
优先级⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

✅ 调用方式代码示例(你的 CarDriver 类)

java

运行

public static void main(String[] args) {
    // ===== 1. 静态变量的调用:推荐用【类名.变量名】,无需创建对象!
    System.out.println(CarDriver.job); // 输出:汽车司机 ✔️ 推荐写法
    
    // 静态变量也可以用对象调用(语法允许,但不推荐,可读性差)
    CarDriver driver = new CarDriver();
    System.out.println(driver.job); // 输出:汽车司机 ✔️ 不推荐
    
    // ===== 2. 实例变量的调用:必须创建对象,用对象调用
    System.out.println(driver.getName()); // 输出:张三 ✔️ 唯一写法
    // System.out.println(CarDriver.name); // 报错 ❌ 类名不能调用实例变量
    
    // ===== 3. 静态变量的核心特性:一处修改,处处生效
    CarDriver.job = "职业司机"; // 修改类的静态变量
    System.out.println(CarDriver.job); // 输出:职业司机
    System.out.println(driver.job);    // 输出:职业司机 → 所有对象都看到修改后的值
}

五、static 静态方法 核心讲解(你写的 MyMath 类完美适配)

讲完静态变量,必须讲静态方法,这是你作业中最常用的 static 场景(比如你写的MyMath类中的add()compare()方法,就应该用 static 修饰),两者是配套使用的。

✅ 1. 什么是静态方法?定义格式

static 关键字修饰的成员方法,称为「静态方法」,也叫「类方法」。

java

运行

public class 类名 {
    // 静态方法:加static修饰
    public static 返回值类型 方法名(参数列表) {
        方法体;
    }
    
    // 实例方法:不加static(你之前写的所有方法都是)
    public 返回值类型 方法名(参数列表) {
        方法体;
    }
}

✅ 2. 静态方法的核心特点(和静态变量一脉相承)

✔ 核心特性①:静态方法属于【类本身】,存在方法区,内存中只存一份

✔ 核心特性②:调用方式(和静态变量完全一样)

  • 推荐:类名.静态方法名(参数) → 无需创建对象,直接调用(最方便!)
  • 不推荐:对象名.静态方法名(参数) → 语法允许,可读性差

✔ 你的代码改造:MyMath 类(最经典的静态方法场景,必改!)

你之前写的MyMath类,是做「通用的数学计算」,不需要创建对象,所有方法都应该用static修饰,这样调用时不用 new 对象,代码简洁到极致!

java

运行

package com.shehuiuniversity;
public class MyMath {
    // ✅ 静态方法:加法计算,通用工具方法,无需创建对象
    public static void add(int a, int b) {
        System.out.println(a + " + " + b + " = " + (a+b));
    }
    
    // ✅ 静态方法:找三个数的最大值,通用工具方法
    public static void compare(double a, double b, double c) {
        double[] arr = {a,b,c};
        double max = arr[0];
        for (double num : arr) {
            if(num > max) max = num;
        }
        System.out.println("最大值为:" + max);
    }
}

✔ 测试类调用(对比你之前的写法,简化太多!)

java

运行

package com.Testclass;
import com.shehuiuniversity.MyMath;
public class MathTest {
    public static void main(String[] args) {
        // 之前的写法:需要new对象,再调用
        // MyMath math = new MyMath();
        // math.add(10,20);
        // math.compare(0.1,0.5,0.3);
        
        // ✅ 静态方法的写法:直接类名调用,无需new对象,超级简洁!
        MyMath.add(10,20); // 输出:10+20=30
        MyMath.compare(0.1,0.5,0.3); // 输出:最大值为0.5
    }
}

👉 为什么 MyMath 类要用静态方法?因为add()compare()通用的工具方法,不依赖任何对象的属性,只是做计算,这种方法用 static 修饰,调用时不需要创建对象,是 Java 开发的标准写法(比如 Java 内置的Math.random()Math.max()都是静态方法)。

✅ 3. 静态方法的【核心规则:能做什么,不能做什么】(高频坑点!必背!)

这是static最容易出错的考点,也是新手写代码最容易报错的地方,所有规则的根源:静态方法加载时,对象还没创建!

✔ ✅ 静态方法中 可以直接调用的内容:

  1. 静态变量(因为静态变量和静态方法一起加载到方法区);
  2. 其他静态方法(同理);

✔ ❌ 静态方法中 绝对不能直接调用的内容(编译报错!):

  1. 实例变量(非 static 的变量)→ 因为实例变量属于对象,静态方法加载时,对象还没创建,内存中没有实例变量;
  2. 实例方法(非 static 的方法)→ 因为实例方法依赖对象,静态方法加载时,对象还没创建;

✔ 补充规则:

静态方法中可以间接调用实例变量 / 方法 → 必须手动创建对象,通过对象调用即可。

✔ 规则代码示例(一目了然)

java

运行

public class TestStatic {
    // 静态变量
    public static String strStatic = "静态变量";
    // 实例变量
    public String strInstance = "实例变量";
    
    // 静态方法
    public static void staticMethod() {
        System.out.println(strStatic); // ✅ 静态方法调用静态变量:允许
        staticMethod2(); // ✅ 静态方法调用静态方法:允许
        
        // System.out.println(strInstance); // ❌ 静态方法调用实例变量:报错!
        // instanceMethod(); // ❌ 静态方法调用实例方法:报错!
        
        // ✅ 间接调用实例变量/方法:创建对象后调用
        TestStatic ts = new TestStatic();
        System.out.println(ts.strInstance); // 允许
        ts.instanceMethod(); // 允许
    }
    
    // 静态方法2
    public static void staticMethod2() {
        System.out.println("静态方法2");
    }
    
    // 实例方法
    public void instanceMethod() {
        // ✅ 实例方法可以调用任何内容:静态的+实例的(无任何限制)
        System.out.println(strStatic);
        System.out.println(strInstance);
        staticMethod();
    }
}

六、static 静态代码块 核心讲解(补充知识点,作业 / 面试常用)

除了静态变量、静态方法,还有一个static的常用语法:静态代码块,也是面试高频考点,顺带讲完,内容完整。

✅ 1. 定义格式

写在类中,方法外,被static修饰的代码块,没有方法名,没有参数,没有返回值:

java

运行

public class 类名 {
    // 静态代码块
    static {
        代码体; // 初始化静态变量、执行类的初始化逻辑
    }
}

✅ 2. 核心特点

  1. 执行时机:类加载时自动执行,且只执行一次(不管创建多少个对象,静态代码块只执行一次);
  2. 执行顺序:静态代码块 → 构造方法(静态代码块更早);
  3. 作用:专门用于初始化静态变量,或者执行一些需要在类加载时就完成的初始化逻辑;

✅ 3. 代码示例(你的 CarDriver 类适配)

java

运行

public class CarDriver {
    public static String job;
    private String name;
    private int age;
    
    // 静态代码块:初始化静态变量,类加载时执行,只执行一次
    static {
        job = "汽车司机";
        System.out.println("静态代码块执行了 → 初始化职业为司机");
    }
    
    // 构造方法:创建对象时执行,创建一次执行一次
    public CarDriver() {
        System.out.println("构造方法执行了 → 创建司机对象");
    }
}

调用测试:

java

运行

public static void main(String[] args) {
    CarDriver driver1 = new CarDriver();
    CarDriver driver2 = new CarDriver();
}

执行结果:

plaintext

静态代码块执行了 → 初始化职业为司机
构造方法执行了 → 创建司机对象
构造方法执行了 → 创建司机对象

👉 结论:静态代码块只执行一次,构造方法创建几个对象执行几次。


七、static 的核心使用场景(总结,学完就会用,不踩坑)

结合你写的所有代码(CarDriver、MyMath、字符串工具类),整理出static3 个最常用场景,也是开发中的标准用法,按优先级排序,你作业中用到的场景都在里面

✅ 场景 1:定义【通用工具类的方法】→ 必用 static(你的 MyMath 类)

比如:数学计算(add、compare)、字符串工具(拼接、判断)、数组工具(排序、找最值),这类方法是通用的,不依赖任何对象的属性,用 static 修饰后,调用时无需创建对象,直接类名调用,代码简洁高效。

Java 内置的工具类都是这样写的:java.lang.Mathjava.util.Arraysjava.util.Collections 中的所有方法都是静态方法。

✅ 场景 2:定义【所有对象共享的常量 / 属性】→ 必用 static(你的 CarDriver 类)

比如:所有司机的职业、所有学生的学校名称、所有商品的品牌,这类属性值所有对象都一样,用 static 修饰后,内存中只存一份,极大节省内存。

✅ 场景 3:定义【统计类的全局数据】→ 必用 static

比如:统计创建了多少个司机对象、统计程序的运行次数、统计用户的登录次数,这类数据需要全局共享,用 static 变量存储,一处修改,处处生效。


八、static 的高频坑点(避坑指南,新手必看)

✅ 坑 1:滥用 static,把所有变量 / 方法都加 static

比如:把司机的 name、age 也加 static,结果所有司机的姓名和年龄都变成一样的,这是最常见的错误!

✔ 原则:独有属性用实例变量,共享属性用静态变量

✅ 坑 2:静态方法中调用实例变量 / 方法,编译报错

根源:静态方法加载时,对象还没创建,内存中没有实例变量,记住规则即可避免。

✅ 坑 3:用对象名调用静态变量 / 方法(语法允许,但不推荐)

比如:driver.jobmath.add(10,20),虽然不报错,但可读性差,别人看代码时不知道这是静态的,标准写法永远是:类名。静态变量 / 方法

✅ 坑 4:认为 static 的内容是「全局唯一」的,就可以随便修改

静态变量是类的共有资源,一处修改,所有对象都会受影响,修改时要谨慎,避免误改导致程序逻辑错误。


九、最终总结(所有知识点精华,背下来 = 掌握所有 static 考点)

✅ 核心一句话

static的本质是:把资源从【对象私有】变为【类公有】,内存中只存一份,所有对象共享,节省内存,简化调用

✅ 核心记忆点

  1. 静态变量:方法区、一份内存、类加载初始化、类名调用、所有对象共享;
  2. 实例变量:堆内存、多份内存、对象创建初始化、对象名调用、对象独有;
  3. 静态方法:类名调用、不能直接调用实例内容、适合做通用工具方法;
  4. 静态代码块:类加载执行一次、初始化静态变量;
  5. 使用原则能不用 static 就不用,必须用的时候才用 → 独有属性用实例,共享属性 / 工具方法用静态。

十、附赠:你之前的 MyMath 类 + CarDriver 类 最终优化版(带 static,完美版本)

✅ 优化后 MyMath 类(静态方法,推荐写法)

java

运行

package com.shehuiuniversity;
public class MyMath {
    // 静态方法:加法
    public static void add(int a, int b) {
        System.out.println(a + " + " + b + " = " + (a+b));
    }
    // 静态方法:找三个数最大值
    public static void compare(double a, double b, double c) {
        double max = a > b ? (a > c ? a : c) : (b > c ? b : c);
        System.out.println("参与比较的数值:" + a + "," + b + "," + c + ",最大值为:" + max);
    }
}

✅ 优化后 CarDriver 类(静态变量 + 实例变量)

java

运行

package com.shehuiuniversity;
import java.util.Scanner;
public class CarDriver {
    // 静态变量:所有司机共享
    public static String job = "汽车司机";
    // 实例变量:每个司机独有
    private String name;
    private String gender;
    private int age;
    private String license;
    private String carNum;

    // set/get方法
    public void setName() {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入姓名:");
        this.name = sc.nextLine();
    }
    public void setGender() {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入性别:");
        this.gender = sc.nextLine();
    }
    public void setAge() {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入年龄:");
        this.age = sc.nextInt();
        sc.nextLine();
    }
    public void setLicense() {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入驾驶证号:");
        this.license = sc.nextLine();
    }
    public void setCarNum() {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入车牌号:");
        this.carNum = sc.nextLine();
    }
    public String getName() { return name; }
    public String getGender() { return gender; }
    public int getAge() { return age; }
    public String getLicense() { return license; }
    public String getCarNum() { return carNum; }

    // 实例方法
    public void wayToDrive() {
        System.out.println(name + "(" + job + ")正在开车牌号为" + carNum + "的车");
    }

    // toString方法
    @Override
    public String toString() {
        return "【"+job+"信息】\n姓名:"+name+"\n性别:"+gender+"\n年龄:"+age+"\n驾驶证号:"+license+"\n车牌号:"+carNum;
    }
}

✅ 测试类

java

运行

package com.Testclass;
import com.shehuiuniversity.MyMath;
import com.shehuiuniversity.CarDriver;
public class TestAll {
    public static void main(String[] args) {
        // 调用静态方法:无需创建对象
        MyMath.add(10,20);
        MyMath.compare(0.1,0.5,0.3);
        
        // 调用静态变量:类名调用
        System.out.println("职业:" + CarDriver.job);
        
        // 创建对象调用实例方法
        CarDriver driver = new CarDriver();
        driver.setName();
        driver.setGender();
        driver.setAge();
        driver.setLicense();
        driver.setCarNum();
        System.out.println(driver);
        driver.wayToDrive();
    }
}

至此,static的所有核心知识点你就全部掌握了,这是 Java 基础的重中之重