
深入理解Java中的变量遮蔽
在java面向对象编程中,继承是实现代码复用和多态性的核心机制。然而,如果不恰当使用,继承也可能引入一些不易察觉的问题,其中之一便是“变量遮蔽”(variable shadowing)。变量遮蔽发生在子类声明了一个与父类中已存在的实例变量同名且同类型的变量时。此时,子类中的同名变量会“遮蔽”父类中的变量,使得子类实例在访问该变量时,默认访问的是子类自己的变量,而不是父类的变量。这与方法重写(method overriding)不同,方法重写是运行时多态的体现,而变量遮蔽则是一种编译时绑定行为。
案例分析:开关设备的异常行为
考虑一个旨在演示依赖反转原则(DIP)的开关系统。我们有一个抽象基类Switchable,它定义了设备的开关状态state以及turn_on()和turn_off()抽象方法。Lamp和Television是继承自Switchable的具体设备。PowerSwitch类负责通过调用Switchable接口的方法来控制设备。
初始代码结构如下:
// 定义设备状态枚举
public enum State {
on, off;
}
// 抽象基类:定义可切换设备的通用接口和状态
public abstract class Switchable {
public State state; // 基类中的状态变量
abstract public void turn_on();
abstract public void turn_off();
}
// Lamp 类:具体设备实现
public class Lamp extends Switchable {
public State state; // 子类中再次声明了同名状态变量,遮蔽了父类的state
public Lamp() {
state = State.off; // 初始化子类自己的state
}
public void turn_on() {
this.state = State.on; // 修改的是子类自己的state
System.out.println("lamp's on");
}
public void turn_off() {
this.state = State.off; // 修改的是子类自己的state
System.out.println("lamp's off");
}
}
// Television 类:另一个具体设备实现
public class Television extends Switchable {
public State state; // 子类中再次声明了同名状态变量
public Television() {
state = State.off;
}
public void turn_on() {
this.state = State.on;
System.out.println("lamp's on"); // 注意:这里也错误地打印了"lamp's on"
}
public void turn_off() {
this.state = State.off;
System.out.println("lamp's off"); // 注意:这里也错误地打印了"lamp's off"
}
}
// PowerSwitch 类:负责控制Switchable设备
public class PowerSwitch {
Switchable sw;
public PowerSwitch(Switchable sw) {
this.sw = sw;
}
public void ClickSwitch() {
// 这里访问的是Switchable引用sw所指向对象中继承自Switchable的state
if (sw.state == State.off) {
sw.turn_on();
} else {
sw.turn_off();
}
}
}
// 主函数:测试代码
public class Main {
public static void main(String[] args) {
Switchable sw = new Lamp();
PowerSwitch ps = new PowerSwitch(sw);
ps.ClickSwitch(); // 第一次点击
ps.ClickSwitch(); // 第二次点击
}
}在上述代码中,当我们执行Main方法并期望PowerSwitch能正确地切换Lamp的状态时,实际输出却是两次“lamp's off”。这是因为Lamp类内部再次声明了一个public State state;变量,它“遮蔽”了从Switchable基类继承的同名变量。
具体分析如下:
立即学习“Java免费学习笔记(深入)”;
- Switchable基类中的state变量未初始化。 作为一个实例变量,它将默认初始化为null。
- PowerSwitch.ClickSwitch()方法中的条件判断: if (sw.state == State.off)。由于sw是一个Switchable类型的引用,它访问的是Lamp对象中继承自Switchable的那个state变量。因为这个变量是null,null == State.off的判断结果是false。
- 始终进入else分支: 由于条件判断始终为false,PowerSwitch会一直调用sw.turn_off()。
- Lamp.turn_off()方法的行为: Lamp类中的turn_off()方法修改的是this.state,这里的this.state指的是Lamp类自己声明的那个state变量(即被遮蔽的变量),并打印“lamp's off”。
结果是,PowerSwitch所依赖的state(基类变量)始终为null,从未被改变,因此每次都触发turn_off()。而Lamp内部的state(子类变量)虽然被修改,但PowerSwitch无法感知。这种逻辑上的断裂导致了程序行为与预期不符。
解决方案:消除变量遮蔽
解决此问题的关键在于消除子类中的变量遮蔽。一个对象在继承体系中,对于同一个逻辑属性,应该只维护一个实例变量。正确的做法是让Lamp和Television类直接使用从Switchable基类继承的state变量,而不是重新声明一个。
修改后的代码示例如下:










