Java static 关键字从浅入深
Java static 关键字从浅入深
前言
static 是 Java 里非常常见的关键字。它的核心意思是:这个成员属于类,而不是属于某一个对象。
如果把类比作一张“设计图”,对象就是按设计图造出来的一台台机器。普通字段是每台机器自己的零件,static 字段则像挂在工厂墙上的公共看板,所有机器看到的是同一份。
一、static 的基本概念
1.1 static 修饰什么
static 可以修饰变量、方法、代码块、内部类。
常见形式:
1 | public class Demo { |
不能直接修饰普通外部类,也不能修饰局部变量。
1.2 static 的一句话理解
普通成员依赖对象,static 成员依赖类。
所以普通成员通常用 对象.成员 访问,静态成员通常用 类名.成员 访问。
二、static 变量
2.1 类变量与实例变量
被 static 修饰的变量叫类变量。它在类加载后只有一份,被这个类的所有对象共享。
实例变量属于对象,每 new 一次就有一份。
1 | class User { |
这里 name 是每个用户自己的名字,total 是所有用户共同维护的总人数。
2.2 使用场景
static 变量适合存放“全班共享”的数据。
常见场景:
1 | public class AppConfig { |
适合放常量、计数器、共享配置、缓存引用。
不适合放用户登录状态、请求参数、订单数据等会因对象或线程变化而变化的数据。
三、static 方法
3.1 静态方法的特点
静态方法属于类,可以不创建对象直接调用。
1 | Math.max(1, 2); |
静态方法中不能直接访问非静态字段,因为它执行时不一定有对象。
1 | class Demo { |
原因很简单:你站在工厂办公室里,能看公共看板,但不能知道某一台机器里私有零件的状态,除非你拿到那台机器的引用。
3.2 使用场景
静态方法适合无状态工具方法。
1 | public class StringUtils { |
如果方法强依赖对象状态,就不要写成 static。
四、static 代码块
4.1 static 代码块什么时候执行
静态代码块在类初始化时执行,并且一个类只执行一次。
1 | class Demo { |
常见触发时机包括:创建对象、访问静态字段、调用静态方法、反射加载类。
4.2 初始化顺序
大致顺序是:
1 | 类加载 -> 静态字段赋值和静态代码块 -> 实例字段和实例代码块 -> 构造方法 |
静态内容先于对象存在。它像工厂开门前先装好的公共设备,对象只是后面生产出来的产品。
五、static 内部类
5.1 静态内部类
静态内部类不依赖外部类对象。
1 | class Outer { |
普通内部类依赖外部类对象。
1 | class Outer { |
5.2 使用场景
静态内部类常用于把辅助类收在外部类内部,表达“它只服务于这个外部类”的关系。
经典用法是 Holder 单例:
1 | public class Singleton { |
它利用类初始化的线程安全特性,实现懒加载和安全发布。
外部类加载
- 当 JVM 加载
Singleton类时:- 会加载外部类的类信息(类结构、方法、字段信息)。
- 不会立刻加载静态内部类 Holder。
- 也就是说,即使外部类被加载,内部类仍然“睡着”,不会初始化其中的 static 字段。
类比:外部类是房子,内部类是保险箱。房子建好了,保险箱没开,里面的东西不会动。
静态内部类的加载延迟到第一次使用。
这里的第一次使用是指:
- 调用 Holder 的 static 字段或方法。
六、static 与内存模型
6.1 static 存在哪里
很多旧资料会说 static 变量在方法区。更准确地说,类的元数据在方法区的实现中,HotSpot 从 JDK 8 开始使用元空间保存类元数据;而静态字段的实际存储与类对象相关,底层实现细节和 JVM 版本有关。
学习时可以先记住:static 成员和类生命周期绑定,不和某个实例对象绑定。
6.2 类加载与初始化
Java 类从被 JVM 认识到可使用,会经历加载、验证、准备、解析、初始化等阶段。
对 static 来说,重点是:
准备阶段会给静态变量分配默认值。
初始化阶段会执行显式赋值和 static 代码块。
1 | static int count = 10; |
准备阶段:count = 0。
初始化阶段:count = 10。
6.3 线程安全思考
static 变量是共享的,共享就可能有并发问题。
1 | private static int count; |
count++ 不是原子操作,多线程下可能丢失更新。项目中可用 AtomicInteger、锁、不可变对象,或者避免共享可变静态状态。
七、项目中如何使用 static
7.1 推荐使用
推荐用于常量:
1 | public static final int MAX_RETRY = 3; |
推荐用于无状态工具方法:
1 | public static String normalize(String value) { |
推荐用于 Holder 单例:
1 | private static class Holder { |
7.2 谨慎使用
谨慎使用可变静态变量。
1 | public static List<String> users = new ArrayList<>(); |
它容易带来全局状态污染、测试互相影响、线程安全问题和难以追踪的数据变化。
项目经验里,static 最大的问题不是不会用,而是用得太方便。
八、面试如何考察 static
8.1 高频问题
常见问题:
static 变量和实例变量区别是什么?
static 方法能不能访问普通成员?
static 代码块什么时候执行?
类加载初始化顺序是什么?
static final 和普通 static 有什么区别?
静态内部类为什么能实现单例?
静态变量线程安全吗?
8.2 回答思路
先讲归属:static 属于类,普通成员属于对象。
再讲生命周期:static 随类加载初始化,普通成员随对象创建初始化。
再讲访问限制:静态上下文没有 this,不能直接访问实例成员。
最后讲项目风险:共享可变状态要注意并发和测试污染。
总结
static 的本质是“属于类,而不是属于对象”。
用得好,它可以表达常量、工具方法、类级共享资源和单例结构;用得不好,它会变成全局变量,制造并发问题和测试污染。
记住一句话:能不共享就不共享,必须共享就尽量不可变;如果共享可变状态,就要认真考虑线程安全和生命周期。