这是一个非常核心且重要的概念,理解它对掌握 Java 至关重要。

核心概念:一句话总结
- 静态成员(变量/方法/代码块/内部类):属于 类(Class) 本身,它在内存中只有一份副本,所有对象共享这一份,它不依赖于任何具体的对象实例。
- 非静态成员(变量/方法/内部类):属于 对象(Instance),每创建一个对象,就会为该对象创建一份独立的副本,它必须通过具体的对象来访问。
详细对比表格
为了更清晰地展示区别,我们用一个表格来对比:
| 特性 | 非静态 | 静态 |
|---|---|---|
| 所属 | 属于 对象 (Instance) | 属于 类 (Class) |
| 内存分配 | 在创建对象时分配内存,堆中。 | 在类加载时分配内存,方法区(或称静态区)。 |
| 生命周期 | 随对象的创建而诞生,随对象的垃圾回收而消亡。 | 随类的加载而诞生,随类的卸载而消亡(通常与 JVM 生命周期相同)。 |
| 访问方式 | 必须通过 对象实例 来访问。 | 可以通过 类名 或 对象实例 来访问(推荐使用类名)。 |
| 访问权限 | 可以直接访问类中的 静态成员。 | 不能 直接访问非静态成员,必须通过对象实例。 |
| 共享性 | 每个对象都有一份独立的副本,互不影响。 | 所有对象共享同一份副本,一个对象的修改会影响所有其他对象。 |
| 使用场景 | 描述对象自身的特性和行为。name, age, study()。 |
描述整个类的共性或工具性功能,总人数、工具方法。 |
| 代码块 | 普通代码块(),在创建对象时执行。 | 静态代码块(static {}),在类加载时执行,只执行一次。 |
| 内部类 | 成员内部类,与外部类实例相关。 | 静态内部类,与外部类本身相关,不依赖外部类实例。 |
深入解析与代码示例
我们通过一个 Student 类来具体说明。
public class Student {
// --- 非静态成员 ---
// 1. 非静态变量(实例变量)
String name; // 姓名
int age; // 年龄
// 2. 非静态方法(实例方法)
public void study() {
System.out.println(name + " 正在学习...");
}
// --- 静态成员 ---
// 3. 静态变量(类变量)
static int totalStudentCount = 0; // 学生总数
// 4. 静态方法(类方法)
public static void printTotalCount() {
// System.out.println(name); // 错误!静态方法不能直接访问非静态变量 name
System.out.println("当前学生总数为: " + totalStudentCount);
}
// 5. 静态代码块
static {
System.out.println("Student 类被加载了,静态代码块执行!");
totalStudentCount = 0; // 初始化静态变量
}
// 构造方法
public Student(String name, int age) {
this.name = name;
this.age = age;
totalStudentCount++; // 每创建一个学生,总数加一
}
}
非静态成员(实例成员)
public class Main {
public static void main(String[] args) {
// 创建两个学生对象
Student student1 = new Student("张三", 18);
Student student2 = new Student("李四", 19);
// --- 访问非静态成员 ---
// 必须通过对象来访问
System.out.println(student1.name); // 输出: 张三
System.out.println(student2.name); // 输出: 李四
student1.study(); // 输出: 张三 正在学习...
student2.study(); // 输出: 李四 正在学习...
// student1.name 和 student2.name 是两个独立的内存空间,互不干扰。
}
}
分析:
name和age是每个学生对象自己的属性。study()是每个学生对象自己的行为。- 当
new Student()时,JVM 会在堆内存中为student1和student2分别分配空间,存放它们各自的name和age。
静态成员(类成员)
public class Main {
public static void main(String[] args) {
// 创建两个学生对象
Student student1 = new Student("张三", 18);
Student student2 = new Student("李四", 19);
// --- 访问静态成员 ---
// 方式一:通过类名访问(推荐)
System.out.println(Student.totalStudentCount); // 输出: 2
// 方式二:通过对象访问(不推荐,但可以编译通过)
System.out.println(student1.totalStudentCount); // 输出: 2
// 修改静态变量
Student.totalStudentCount = 100;
System.out.println(student2.totalStudentCount); // 输出: 100
System.out.println(Student.totalStudentCount); // 输出: 100
// 调用静态方法
Student.printTotalCount(); // 输出: 当前学生总数为: 100
}
}
分析:

totalStudentCount属于Student这个类,而不是某个具体的学生。- 无论创建多少个
Student对象,totalStudentCount在内存中只有一份。 - 当
student1修改了totalStudentCount为 100,student2看到的也是 100,它们共享同一个数据。 - 静态方法
printTotalCount也是类级别的,它可以直接访问静态变量totalStudentCount,但不能访问非静态的name,因为它不知道name是哪个对象的。
静态代码块
静态代码块在类加载时执行,且只执行一次,通常用于进行类的静态初始化操作。
public class TestStaticBlock {
static {
System.out.println("静态代码块 1 执行!");
}
public static void main(String[] args) {
System.out.println("main 方法开始");
TestStaticBlock t1 = new TestStaticBlock();
TestStaticBlock t2 = new TestStaticBlock();
System.out.println("main 方法结束");
}
static {
System.out.println("静态代码块 2 执行!");
}
}
输出结果:
静态代码块 1 执行!
静态代码块 2 执行!
main 方法开始
main 方法结束
分析:
- 当 JVM 第一次加载
TestStaticBlock类时,会按顺序执行所有静态代码块。 - 静态代码块的执行只与类的加载有关,与创建对象无关。
main方法中创建t1和t2时,静态代码块不会再执行。
何时使用静态?
-
静态变量:
- 当某个数据需要被所有对象共享时,网站在线人数、学校学生总数。
- 当定义一个常量时(
public static final)。Math.PI。
-
静态方法:
- 当一个方法不依赖于任何对象的状态(即不访问任何实例变量)时。
- 工具类中的方法,如
Arrays.sort(),Math.max()。 - 工厂方法,用于创建对象实例,如
Calendar.getInstance()。
-
静态代码块:
- 初始化静态变量。
- 加载 native 库(
.dll或.so文件)。
总结与关键点
- 内存是根本:记住静态成员在方法区,非静态成员在堆中,这是所有区别的根源。
- 访问规则:非静态可以访问静态,但静态不能访问非静态,因为静态诞生时,非静态对象可能还没创建。
- 访问方式:静态成员优先用
类名.访问,这能清晰地表明它是一个类级别的成员,也避免了因通过对象访问可能带来的混淆(虽然对象也能访问)。 - 不要滥用静态:静态成员的生命周期很长,且是共享的,如果滥用(将所有方法都写成静态的),可能会导致程序状态混乱,不利于面向对象的设计(封装、多态等)。
理解了静态和非静态的区别,你就掌握了 Java 面向对象编程的一个基石。
