← [ BACK_TO_NOTES ]

Java 变量与作用域

Java 局部变量、实例字段与类变量的差异:作用域、生命周期、默认值与“明确赋值”规则。

#concept #growing #dev #java

[!info] related notes

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. 请求级数据优先局部变量,避免把线程请求状态乱塞进单例字段。
  2. 字段默认值会掩盖“忘了初始化”——对象图里尤其危险。
  3. 明确赋值是编译期规则,不是运行时“碰巧有值”。

核心概念与准确模型

1. 局部变量

  • 声明在方法、构造器、静态/实例初始化块或语句块
  • 普通块内声明的作用域从声明处开始(含初始化器)到块结束;for 初始化器、资源声明、模式变量等有各自规则。
  • 必须明确赋值后才能读取(definitely assigned)
  • 方法/构造器参数与局部变量是规范中的不同变量类别;参数在进入方法/构造器体前已由实参赋值。

2. 实例字段

  • 每个对象一份
  • 有默认值:数值 0、boolean false、引用 null
  • 字段属于对象,不能把局部变量的词法作用域结束等同于对象被回收;对象是否仍可达另行判断。

3. 类变量(static)

  • 每个运行时类一份;同名类被不同加载器分别定义时,其 static 字段也独立。
  • 同样有默认值
  • 适合真正的共享常量/无状态配置入口;滥用会成隐式全局

4. 作用域嵌套

  • 内层块可声明自己的局部变量
  • 不能在同一方法中用内层变量“重复声明”外层同名局部变量(规则严格,编译器会拦)
  • 字段可被同名局部变量遮蔽(shadowing)——可读性杀手

5. final 变量(入口)

  • 局部 final:至多赋值一次,读取前必须明确赋值;声明后从未使用的 final 局部变量可以一直未赋值。
  • 空白 final 字段必须在每条正常完成的构造路径(实例)或静态初始化路径(static)上明确赋值;final 引用不代表对象内容不可变。
  • 详细构造顺序见 字段、构造器与 this

边界情况与反直觉行为

  1. 引用字段默认 null,未初始化便解引用会 NPE;基本类型字段有各自默认值,不是 null。
  2. 循环里的局部变量每次迭代语义要结合是否重新声明理解。
  3. lambda 捕获局部变量/参数要求其为 final 或有效 final;增强 for 本身不要求循环变量 final,给循环变量重新赋值也不会替换数组或集合中的元素。
  4. 遮蔽字段时 this.x = x 是构造器常见写法,见后续构造器篇。

常见误区

[!warning] 常见误区:所有变量都有默认值 错误理解:局部 int 自动是 0。
正确模型:字段和新数组元素有默认值;普通局部变量必须在读取前明确赋值。 为何易错:与字段行为不一致。

[!warning] 常见误区:static 字段当请求上下文 错误理解:用静态 Map 存当前用户最省事。
正确模型:请求上下文应用参数/ThreadLocal(克制)/框架上下文,并注意泄漏。
为何易错:演示代码常用 static 偷懒。

工程实践

  1. 尽量缩小局部变量作用域,靠近使用处声明。
  2. 字段初始化能在声明处表达清楚的,写清楚;复杂初始化放构造器。
  3. 共享可变 static 视为危险区,优先不可变常量。
  4. 命名避免 a1/tmp2;业务名即文档。

本节总结

  • 位置决定变量种类与生命周期
  • 局部:明确赋值;字段:默认值
  • 作用域与遮蔽规则影响可读性与正确性

自测题

  1. 为什么局部变量没有默认值是一种设计利好?
  2. 实例字段 String name; 的默认值是什么?
参考答案
  1. 迫使编译期暴露“未初始化就使用”,减少垃圾值缺陷。
  2. null。

延伸阅读与资料来源

资料类型支撑内容
JLS Definite Assignment规范明确赋值
JLS Types, Values, and Variables规范变量种类
创建于 2026/7/15 更新于 2026/10/6
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本