Java 变量与作用域
Java 局部变量、实例字段与类变量的差异:作用域、生命周期、默认值与“明确赋值”规则。
#type / concept
#status / growing
#tech / dev
#resource / java
[!info] 关联笔记
Java 变量与作用域
这个概念为什么出现
同一套“变量”词汇在 Java 里至少分三类语境:
- 局部变量:方法/构造器/代码块内
- 实例字段:属于对象
- 类变量(static 字段):属于类
再叠加:局部变量没有默认值,使用前必须明确赋值;字段则有默认值。这是大量编译错误与 NPE 叙事的分界线。
[!abstract] 一句话理解 变量的含义取决于声明位置:局部变量受块作用域与明确赋值约束;字段随对象或类存在并带默认值;作用域决定名字在哪可见、生命周期决定值活多久。
最小可运行示例
先把示例放进业务场景,再看代码:
场景:积分账户服务在一次请求里计算应付积分
请求级局部变量保存入参,对象字段保存账户余额,静态字段保存服务名。
教学点:三类变量、字段默认值、局部变量必须赋值。
public class PointAccount {
// 类变量:所有账户共享的服务标识(示例用,真实配置勿硬编码)。
static String serviceName = "point-service";
// 实例字段:每个账户对象各自一份余额;数值字段默认 0。
int balance;
// credit:给账户加积分。
//
// 业务意图:把请求增量落到账户余额,并返回到账后余额。
// 教学点:
// - 参数 delta 是局部变量(方法参数);
// - 未赋值局部变量不能读;
// - 字段 balance 不写也有 0 默认值。
int credit(int delta) {
// 局部变量:只在方法内存活。
int before = balance;
balance = before + delta;
return balance;
}
public static void main(String[] args) {
PointAccount acc = new PointAccount();
// 字段默认 0,因此首充前余额为 0。
int after = acc.credit(100);
System.out.println(serviceName + " balance=" + after);
// int unknown;
// System.out.println(unknown); // 编译错误:可能尚未初始化
}
}
建议运行:
javac PointAccount.java && java PointAccount
期望输出:
point-service balance=100
结合场景再看三个关注点
- 请求级数据优先局部变量,避免把线程请求状态乱塞进单例字段。
- 字段默认值会掩盖“忘了初始化”——对象图里尤其危险。
- 明确赋值是编译期规则,不是运行时“碰巧有值”。
核心概念与准确模型
1. 局部变量
- 声明在方法、构造器、静态/实例初始化块或语句块
- 作用域:从声明点到所在块结束
- 必须明确赋值后才能读取(definitely assigned)
- 参数是已赋值的局部变量
2. 实例字段
- 每个对象一份
- 有默认值:数值 0、
boolean false、引用null - 生命周期随对象可达性(GC 相关后置)
3. 类变量(static)
- 每个类一份(在同一类加载器语境下的常见模型)
- 同样有默认值
- 适合真正的共享常量/无状态配置入口;滥用会成隐式全局
4. 作用域嵌套
- 内层块可声明自己的局部变量
- 不能在同一方法中用内层变量“重复声明”外层同名局部变量(规则严格,编译器会拦)
- 字段可被同名局部变量遮蔽(shadowing)——可读性杀手
5. final 变量(入口)
- 局部
final:必须精确赋值一次 - 字段
final:构造结束前赋值完成(实例)或静态初始化完成(static) - 详细构造顺序见 字段、构造器与 this
边界情况与反直觉行为
- 字段是 null 默认,一调用方法就 NPE——不是局部变量规则。
- 循环里的局部变量每次迭代语义要结合是否重新声明理解。
- 增强 for / lambda 捕获对“有效 final”有要求(后置到闭包/lambda 篇)。
- 遮蔽字段时
this.x = x是构造器常见写法,见后续构造器篇。
常见误区
[!warning] 常见误区:所有变量都有默认值 错误理解:局部
int自动是 0。
正确模型:只有字段有默认值;局部必须赋值。
为何易错:与字段行为不一致。
[!warning] 常见误区:static 字段当请求上下文 错误理解:用静态 Map 存当前用户最省事。
正确模型:请求上下文应用参数/ThreadLocal(克制)/框架上下文,并注意泄漏。
为何易错:演示代码常用 static 偷懒。
工程实践
- 尽量缩小局部变量作用域,靠近使用处声明。
- 字段初始化能在声明处表达清楚的,写清楚;复杂初始化放构造器。
- 共享可变 static 视为危险区,优先不可变常量。
- 命名避免
a1/tmp2;业务名即文档。
本节总结
- 位置决定变量种类与生命周期
- 局部:明确赋值;字段:默认值
- 作用域与遮蔽规则影响可读性与正确性
自测题
- 为什么局部变量没有默认值是一种设计利好?
- 实例字段
String name;的默认值是什么?
参考答案
- 迫使编译期暴露“未初始化就使用”,减少垃圾值缺陷。
null。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| JLS Definite Assignment | 规范 | 明确赋值 |
| JLS Types, Values, and Variables | 规范 | 变量种类 |