
1. 从变量生命周期看作用域写Java这么多年我越来越觉得作用域这东西就像变量的“活动范围证明”。你定义一个变量它能在哪些地方被访问、能活多久、什么时候从内存里消失这些规则合在一起就是作用域。很多看起来莫名其妙的编译报错、空指针异常、甚至是内存问题追根溯源都能回到作用域上。1.1 局部变量、成员变量与类变量的本质区别先从最基础的三种变量说起。局部变量定义在方法内部、代码块内部或者是形参列表里成员变量也叫实例变量定义在类内部、方法外部隶属于某个对象实例类变量静态变量用static修饰隶属于类本身所有实例共享一份。区别体现在生命周期上局部变量方法被调用时创建方法执行结束或代码块执行完毕后立即失效存在时间最短。成员变量对象被new出来时分配内存对象变成垃圾被回收时释放生命周期跟对象绑定。类变量类被加载到 JVM 时初始化类被卸载时销毁生命周期最长通常等于程序运行期间。很多人容易忽略的一个细节是局部变量存储在栈帧Stack Frame中而成员变量存储在堆Heap中存储位置的不同直接决定了它们的访问速度和生命周期。栈是线程私有的方法执行完栈帧弹出局部变量天然消失不用垃圾回收器操心。堆是线程共享的成员变量的存活依赖对象引用链是否可达。从开发角度来说该用局部变量绝不用成员变量能缩小作用域就缩小作用域。这不仅仅是为了代码整洁更是从内存效率和线程安全两个维度考虑。我见过不少团队的老代码图省事把一堆临时计算值塞进成员变量结果并发场景下数据互相覆盖排查了半天才定位到是作用域过大导致的无意共享。1.2 代码块作用域的边界问题Java 中一对大括号{}就划出一个作用域边界。方法体是一个代码块if、for、while语句块也各自构成作用域。在某个代码块里声明的变量只能在这个块内使用块结束变量就“失效”。这里有一个让很多新手困惑的问题下面这段代码能编译通过吗for (int i 0; i 10; i) { // do something } System.out.println(i); // 编译报错答案是不能。i是for循环语句内部声明的变量循环结束后i的作用域就结束了循环外访问不到。想在外面拿到循环计数必须在循环前声明变量int i; for (i 0; i 10; i) { // do something } System.out.println(i); // 正常输出 10这个问题的本质是作用域决定了变量的可访问范围而不是变量值是否还存在。循环结束后i的值理论上还在内存某处但语言规范层面已经不允许你通过变量名访问它了。再看一个常见的嵌套代码块场景public void test() { int a 1; { int b 2; System.out.println(a); // 可以访问外层变量 System.out.println(b); // 可以访问内层变量 } System.out.println(b); // 编译报错b 已不在作用域内 }内层代码块可以访问外层代码块中声明的变量反过来不行。这个规则类似于现实中的套间住在里屋的人能去外屋住在外屋的人不能随便进里屋。向外访问靠包容关系向内访问靠禁令。1.3 变量遮蔽现象及其危害当内层作用域中声明了与外层作用域同名的变量时就会发生变量遮蔽Variable Shadowing。在一个大方法里声明一个int result在某个内部代码块里又声明了一个int result这时候内部代码块里使用的result指向的是内层的那个变量外层的result在内层被“藏”了起来。public int calc() { int result 0; for (int i 0; i 10; i) { int result 100; // 编译报错不这要看上下文 } return result; }实际上这种在for内部的局部变量遮蔽外层局部变量的写法在某些情况下是允许的但更多时候会引发混淆。就算编译器放过你代码的可读性也大打折扣。我强烈建议在任何嵌套作用域中避免使用与外层同名的变量。遮蔽问题在大型方法里很难一眼发现等代码跑出诡异结果再回头查往往要浪费半天时间。另一种遮蔽是局部变量遮蔽成员变量这个在实战中太常见了。比如类里有个private int count字段方法参数也叫count这时候方法内直接写count访问的是参数想访问成员变量必须用this.countpublic class Counter { private int count 0; public void add(int count) { count count 10; // 参数自增成员变量纹丝不动 this.count this.count 1; // 这行才修改了成员变量 } }很多刚入行的同学在这里栽过跟头调了半天接口发现数据没变化一查是参数把成员变量遮蔽了赋值全赋给了局部参数。我的习惯是入参名跟成员变量相同时涉及成员变量的操作一律显式写this绝不靠“我觉得编译器能分清”来偷懒。2. 类级别作用域与访问控制变量的作用域不只是看它定义在哪个代码块里还要看它对其他类是否可见。Java 用访问修饰符来管理这个层面的可见性这是 Java 面