大家好,这里是后端基础拾光集。
static 这个关键字,几乎每个 Java 项目里都能看到。 但很多人写到它时,只知道 “加了 static 就能直接调用”,至于为什么能调用、什么时候该用、什么时候不能用,却说不清楚。
接下来我们通过用「班级学生」把static 一次性搞透彻。
一、先建立一个类比模型
把 class 想成一个班级,static 就是贴在班级公告栏上的东西,而不是每个学生自己书包里的东西。
| 类比 | Java 概念 |
|---|---|
| 班级公告栏(全班共用一份) | 静态变量 / 静态方法 |
| 每个学生自己的书包(人手一份) | 实例变量 /成员变量/ 实例方法 |
| 开学第一天全班一起布置公告栏 | 静态代码块 |
二、static 变量:全班级共用的一份
我们可以把类想象成一个班级。 普通成员变量,是每个学生自己的作业本,每个对象都有一份。 而 static 变量,是贴在教室墙上的班级花名册,全班共用一份。
class Student {
private String name; // 普通成员变量:每个学生自己的名字
private static String classRoom; // 静态变量:全班共用的教室
}
name:new Student()一次,就多一份,各自独立。school:不管 new 多少个学生,内存里只有一份,所有学生共享。
Student a = new Student(); a.name = "小明";
Student b = new Student(); b.name = "小红";
Student.classRoom = "二班"; // 改一次
System.out.println(a.school); // 二班
System.out.println(b.school); // 二班 —— 大家都变了
关键点:静态变量属于类,不属于对象。 只要这个类被加载,static 变量就会存在。 任何一个对象修改它,其他对象看到的都是修改后的值。 用 类名.变量 访问才是规范写法。
适用场景:真正全局共享、与具体对象无关的数据。
- 常量:
static final double PI = 3.14159; - 计数器:统计一共创建了多少个学生
- 配置项、全局缓存
一句话记忆:普通变量属于对象,static 变量属于类。
三、static 方法:不需要学生本人,也能使用的公共饮水机
普通成员方法,必须先有一个具体对象才能调用,就像必须找到某个学生,才能看他的作业本。 static 方法不依赖具体对象,可以直接通过类名调用,就像教室里的公共饮水机,谁都可以用。
正因为不依赖对象,static 方法里不能直接访问普通成员变量或普通成员方法。 它能访问的,主要是 static 成员,以及方法参数里传入的数据。
class Student {
static int count = 0;
static int getCount() { // 静态方法
return count;
}
void sayHello() { // 普通成员方法
System.out.println("我是学生");
}
}
调用方式对比:
Student.getCount(); // 静态方法:类名直接调,不需要对象
Student s = new Student();
s.sayHello(); // 普通成员方法:必须先有对象
核心规则(也是面试常考):
静态方法里不能直接访问实例变量和实例方法。
为什么?因为静态方法可以在没有任何对象的情况下被调用,而实例变量/方法必须依附于某个对象才存在。你没对象,凭什么用别人的书包?
class Student {
String name;
static void printName() {
System.out.println(name); // ❌ 编译报错!name 是哪个学生的?
}
}
反过来,实例方法可以访问静态变量/静态方法(因为对象一定存在于类之中)。
适用场景:工具类方法,与具体对象无关。
Math.max()、Arrays.sort()、Integer.parseInt()- 工厂方法、单例的
getInstance()
四、static 代码块:开学第一天,全班一起布置
static 代码块会在类第一次被加载时执行,而且通常只执行一次,早于任何对象创建。
class Student {
static String classRoom;
static List<String> rules;
static { // 静态代码块
classRoom = "二班";
rules = new ArrayList<>();
rules.add("不准迟到");
System.out.println("静态代码块执行了");
}
}
new Student();
new Student();
new Student();
// 输出:静态代码块执行了 —— 只打印一次!
适用场景:初始化静态资源,且初始化逻辑比较复杂(不止一行赋值)。
- 加载配置文件
- 初始化静态集合、连接池
- 注册驱动
如果只是简单赋值,直接 static int x = 10; 就够了,不用写代码块。
它适合做一些类级别的初始化,比如读取配置、初始化静态资源、建立连接池等。 但别把复杂业务逻辑塞进去,否则类加载过程会变得很重,问题也很难排查。
五、AI 最爱踩的坑
AI 写工具类时,很容易把方法都写成 static。 这对于 “纯工具方法” 来说没问题,比如字符串处理、数值计算。 但如果对象本身有状态,就不该乱用 static。
// ❌ 错误示范:把有状态的计数器写成 static
class Counter {
private static int count = 0;
public void increment() {
count++;
}
}
这里的问题是:count 被所有对象共用。 你创建两个 Counter 对象,各自调用 increment(),结果会互相影响。 这不是对象自己的状态,而是全局状态。
// ✅ 正确示范:状态属于对象,工具行为可以 static
class Counter {
private int count = 0;
public void increment() {
count++;
}
public static boolean isPositive(int value) {
return value > 0;
}
}
六、static 最容易混淆的 4 个问题
问题 1:static 方法里为什么不能直接访问非 static 成员?
因为非 static 成员属于具体对象,而 static 方法调用时,可能根本没有对象存在。 没有对象,就没法访问对象里的成员。
问题 2:static 可以修饰类吗?
只有内部类可以是 static。 普通顶级类不能用 static 修饰。
问题 3:static 变量一定在方法区吗?
基础理解阶段,可以先记: static 变量随着类加载存在,被类的所有对象共享。
问题 4:static 一定线程安全吗?
不一定。 如果多个线程同时修改同一个 static 变量,就会产生线程安全问题。
✅ 核心总结
- 普通变量属于对象,static 变量属于类,被所有对象共享。
- static 方法不依赖对象,可以通过类名直接调用。
- static 方法不能直接访问非 static 成员。
- static 代码块在类第一次加载时执行,常用于类级初始化。
- 纯工具方法可以 static;对象有自己状态时,不要乱用 static。
记住一句话:static 表达的是「属于类、不属于对象」。 每次想加 static,先问自己:这个数据/方法,是不是「没有某个具体对象也应该存在」?是 → 用;不是 → 别用。
📝本文属于专栏「后端基础拾光集」系列。 这个专栏结合生活化案例、线上踩坑实例讲解 Java 基础。 同系列已经更新:ArrayList & LinkedList 源码、HashMap、红黑树、Java 泛型、异常体系、Integer 缓存池、equals & hashCode。 专栏持续更新 Java 后端基础、源码、面试踩坑干货,欢迎一起交流后端学习、面试踩坑经验。