什么是静态变量?
在 Java 中,使用 static 关键字修饰的成员变量被称为静态变量或类变量。

与它相对的是实例变量(没有使用 static 修饰的变量)。
核心区别在于:它们的生命周期和所有者不同。
- 实例变量:属于对象,每创建一个对象,就会为这个对象分配一套独立的实例变量,它们存储在堆内存中,其生命周期与对象的生命周期相同。
- 静态变量:属于类,它不依赖于任何具体的对象,当类被加载到内存时,静态变量就会被分配内存空间,它的生命周期与类的生命周期相同,直到程序结束才会被销毁,无论你创建了多少个对象,静态变量在内存中只有一份拷贝,被所有对象共享。
静态变量的核心特性
1 内存分配与生命周期
- 分配时机:静态变量在类加载时分配内存,而不是在创建对象(
new)时。 - 生命周期:从类加载开始,到整个应用程序结束,它的生命周期比任何对象都长。
- 存储位置:通常存在于方法区(在 JDK 7 及之前)或元空间(在 JDK 8 及之后)的运行时常量池中。
2 共享性
这是静态变量最重要的特性,所有该类的实例都共享同一个静态变量,一个对象对静态变量的修改,会影响到所有其他对象。
3 访问方式
由于静态变量不依赖于对象,所以有两种访问方式:

-
通过类名访问(推荐):
ClassName.staticVariableName;
Student.studentCount -
通过对象名访问(不推荐,但语法允许):
objectName.staticVariableName;
s1.studentCount
(图片来源网络,侵删)
为什么不推荐通过对象名访问? 因为静态变量属于类,而不是对象,通过对象名访问容易造成混淆,让其他开发者误以为这个变量是对象独有的,最佳实践是始终使用类名来访问静态成员。
静态变量 vs. 实例变量
为了更清晰地理解,我们用一个表格来对比:
| 特性 | 静态变量 (类变量) | 实例变量 (对象变量) |
|---|---|---|
| 所属 | 属于类 | 属于对象 |
| 内存拷贝 | 整个类只有一份拷贝,所有对象共享 | 每创建一个对象,就会产生一份独立的拷贝 |
| 内存分配 | 类加载时分配 | 创建对象 (new) 时分配 |
| 生命周期 | 与类相同,随类的加载而创建,随类的卸载而销毁 | 与对象相同,随对象的创建而创建,随对象的回收而销毁 |
| 访问方式 | 类名.变量名 (推荐) 或 对象名.变量名 (不推荐) |
对象名.变量名 |
| 典型用途 | 存储所有对象共享的数据,如:计数器、配置信息、常量等 | 存储对象特有的数据,如:姓名、年龄、ID等 |
代码示例
这个例子将完美地展示静态变量的“共享”特性。
public class Student {
// 实例变量:每个学生有自己的名字和年龄
private String name;
private int age;
// 静态变量:所有学生共享一个班级,记录学生总数
private static String className = "计算机科学1班";
private static int studentCount = 0; // 静态变量,用于计数
// 构造方法
public Student(String name, int age) {
this.name = name;
this.age = age;
// 每当创建一个新学生时,学生总数加1
studentCount++;
System.out.println(name + " 同学加入了班级 " + className);
System.out.println("当前班级总人数: " + studentCount);
}
// 静态方法:可以访问静态变量,但不能直接访问实例变量
public static void printClassName() {
System.out.println("班级名称是: " + className);
// 下面这行代码会编译错误,因为静态方法不知道具体是哪个对象的name
// System.out.println("学生名字是: " + this.name);
}
public static void main(String[] args) {
System.out.println("--- 开始招生 ---");
// 创建第一个学生对象
Student s1 = new Student("张三", 20);
System.out.println("s1 的内存地址: " + s1);
System.out.println("s1 访问的 studentCount: " + s1.studentCount); // 通过对象名访问
System.out.println("通过类名访问的 studentCount: " + Student.studentCount); // 通过类名访问 (推荐)
System.out.println("--------------------");
// 创建第二个学生对象
Student s2 = new Student("李四", 21);
System.out.println("s2 的内存地址: " + s2);
System.out.println("s2 访问的 studentCount: " + s2.studentCount);
System.out.println("--------------------");
// 创建第三个学生对象
Student s3 = new Student("王五", 22);
System.out.println("s3 的内存地址: " + s3);
System.out.println("s3 访问的 studentCount: " + s3.studentCount);
System.out.println("--------------------");
// 验证共享性:通过 s1 修改静态变量
System.out.println("现在通过 s1 修改班级名称...");
s1.className = "软件工程2班"; // 虽然语法允许,但不推荐!
// 推荐写法: Student.className = "软件工程2班";
// 检查 s2 和 s3 的班级名称是否也改变了
System.out.println("s2 的班级名称: " + s2.className); // 输出: 软件工程2班
System.out.println("s3 的班级名称: " + s3.className); // 输出: 软件工程2班
// 调用静态方法
Student.printClassName();
}
}
运行结果分析:
--- 开始招生 ---
张三 同学加入了班级 计算机科学1班
当前班级总人数: 1
s1 的内存地址: com.example.Student@15db9742
s1 访问的 studentCount: 1
通过类名访问的 studentCount: 1
--------------------
李四 同学加入了班级 计算机科学1班
当前班级总人数: 2
s2 的内存地址: com.example.Student@6d06d69c
s2 访问的 studentCount: 2
--------------------
王五 同学加入了班级 计算机科学1班
当前班级总人数: 3
s3 的内存地址: com.example.Student@7852e922
s3 访问的 studentCount: 3
--------------------
现在通过 s1 修改班级名称...
s2 的班级名称: 软件工程2班
s3 的班级名称: 软件工程2班
班级名称是: 软件工程2班
从结果可以看出:
studentCount从 0 开始,每创建一个对象就自增 1,证明所有对象共享同一个studentCount。- 通过
s1修改className后,s2和s3访问到的className也变成了新值,证明所有对象共享同一个className。
静态变量的典型用途
- 计数器:如上面的例子,用来统计创建了多少个对象。
- 共享的配置或常量:数据库连接池的配置、API 的基础 URL 等,所有对象都使用同一个配置。
- 工具类中的共享状态:虽然工具类通常是无状态的,但有时也需要一个共享的缓存或计数器。
- 定义常量:在 Java 中,通常使用
public static final来定义一个常量。static表示它属于类,一份拷贝;final表示它的值不能被修改。public class MathUtils { public static final double PI = 3.14159; } // 使用方式 double radius = 5; double circumference = 2 * MathUtils.PI * radius;
静态变量的限制
- 不能在静态方法中访问非静态成员:静态方法在类加载时就可能存在,此时对象可能还未创建,静态方法无法知道具体是哪个对象的实例变量,所以不能直接访问实例变量或实例方法,反之,实例方法可以访问静态成员,因为它已经依附于一个对象,而这个对象所属的类是已知的。
- 不能使用
this或super关键字:this代表当前对象的引用,而静态成员不属于任何特定对象,this在静态上下文中没有意义。
| 特性 | |
|---|---|
| 本质 | 静态变量是类变量,属于类本身,而不是类的实例。 |
| 内存 | 在类加载时分配,整个 JVM 中只有一份拷贝,被所有实例共享。 |
| 生命周期 | 从类加载开始,到类卸载结束,比对象的生命周期长。 |
| 访问 | 推荐使用 类名.变量名 的方式访问。 |
| 用途 | 适用于所有对象共享的数据,如计数器、共享配置、常量等。 |
| 限制 | 不能在静态方法中直接访问实例变量,也不能使用 this。 |
理解 static 变量是掌握 Java 面向对象编程的关键一步,它能帮助你更好地管理内存和设计高效的程序结构。
