在java面向对象编程中,特别是在实现如依赖反转原则(dip)等设计模式时,开发者可能会遇到条件判断逻辑不按预期工作的问题。一个常见的场景是,当父类和子类中存在同名成员变量时,子类对该变量的修改似乎并未反映到父类引用上,导致基于父类引用的条件判断始终得到错误的结果。
例如,考虑一个智能家居开关系统:一个抽象的Switchable接口定义了可开关设备的通用行为和状态,Lamp和Television是其具体实现。PowerSwitch类负责通过Switchable引用来控制设备开关。预期行为是,每次调用ClickSwitch()方法,设备状态都会在“开”和“关”之间切换。然而,实际运行时,无论调用多少次ClickSwitch(),设备状态似乎都停留在初始的“关”状态,输出始终是“lamp's off”。
初始的代码结构可能如下所示:
// State 枚举 public enum State { on, off; } // 抽象父类 Switchable public abstract class Switchable { public State 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; } 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 (存在与Lamp类似的问题,且打印信息不准确) public class Television extends Switchable { public State state; // 子类重新声明的state变量 public Television() { state = State.off; } public void turn_on() { this.state = State.on; // 修改的是子类的state System.out.println("lamp's on"); // 错误:应为television's on } public void turn_off() { this.state = State.off; // 修改的是子类的state System.out.println("lamp's off"); // 错误:应为television's off } } // 控制器 PowerSwitch public class PowerSwitch { Switchable sw; public PowerSwitch(Switchable sw) { this.sw = sw; } public void ClickSwitch() { // 这里的sw.state访问的是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(); // 第二次点击 } }
在上述代码中,预期的输出应该是“lamp's on”然后是“lamp's off”。然而,实际输出却是两次“lamp's off”。这表明PowerSwitch中的条件判断if(sw.state==State.off)始终为真,导致sw.turn_on()被调用,但sw.state却未被正确更新。
这个问题的核心在于Java的“变量遮蔽”(Variable Shadowing)机制。当子类声明了一个与父类中非private成员变量同名的成员变量时,子类中的这个新变量会“遮蔽”或“隐藏”父类的同名变量。这意味着:
立即学习“Java免费学习笔记(深入)”;
在我们的例子中:
由于Switchable父类中的state变量在Lamp对象被创建时并未被初始化,它默认为null。在PowerSwitch中,if(sw.state == State.off)将导致NullPointerException,或者如果State.off被设计为0,null == 0会是false,从而导致其他非预期行为。为了避免NullPointerException,通常会将父类变量进行默认初始化。即便如此,父类的state变量也从未被子类的方法更新。
解决这个问题的核心在于确保父类和子类共享同一个状态变量,而不是各自拥有独立的变量。正确的做法是,只在父类Switchable中声明state变量,并让所有子类继承并使用这个唯一的变量。
具体步骤如下:
以下是修正后的代码:
// State 枚举 (保持不变) public enum State { on, off; } // 抽象父类 Switchable (state变量在此处声明并初始化) public abstract class Switchable { public State state = State.off; // 统一在此处初始化 abstract public void turn_on(); abstract public void turn_off(); } // 子类 Lamp (移除重复的state变量声明) public class Lamp extends Switchable { // 不再声明 public State state; public Lamp() { // 构造器无需再初始化state,它已在父类中初始化 } @Override // 明确表示覆盖父类方法 public void turn_on() { this.state = State.on; // 直接使用继承的state变量 System.out.println("lamp's on"); } @Override // 明确表示覆盖父类方法 public void turn_off() { this.state = State.off; // 直接使用继承的state变量 System.out.println("lamp's off"); } } // 子类 Television (移除重复的state变量声明,并修正打印信息) public class Television extends Switchable { // 不再声明 public State state; public Television() { // 构造器无需再初始化state } @Override // 明确表示覆盖父类方法 public void turn_on() { this.state = State.on; // 直接使用继承的state变量 System.out.println("television's on"); // 修正打印信息 } @Override // 明确表示覆盖父类方法 public void turn_off() { this.state = State.off; // 直接使用继承的state变量 System.out.println("television's off"); // 修正打印信息 } } // 控制器 PowerSwitch (保持不变,因为其逻辑是正确的,问题在于变量遮蔽) public class PowerSwitch { Switchable sw; public PowerSwitch(Switchable sw) { this.sw = sw; } public void ClickSwitch() { // 这里的sw.state现在访问的是Switchable中声明的唯一state变量 if (sw.state == State.off) { sw.turn_on(); } else { sw.turn_off(); } } } // 主程序 (保持不变) public class Main { public static void main(String[] args) { Switchable lamp = new Lamp(); PowerSwitch lampSwitch = new PowerSwitch(lamp); System.out.println("Testing Lamp:"); lampSwitch.ClickSwitch(); // 第一次点击:从off -> on lampSwitch.ClickSwitch(); // 第二次点击:从on -> off lampSwitch.ClickSwitch(); // 第三次点击:从off -> on System.out.println("\nTesting Television:"); Switchable tv = new Television(); PowerSwitch tvSwitch = new PowerSwitch(tv); tvSwitch.ClickSwitch(); // 第一次点击:从off -> on tvSwitch.ClickSwitch(); // 第二次点击:从on -> off } }
修正后的输出示例:
Testing Lamp: lamp's on lamp's off lamp's on Testing Television: television's on television's off
现在,每次调用ClickSwitch()方法,sw.state都会正确地反映设备的当前状态,从而实现预期的开关逻辑。
变量遮蔽是Java继承中一个常见的陷阱,它会导致程序逻辑与预期不符,尤其是在涉及多态引用和条件判断时。通过理解变量遮蔽的机制,并遵循在父类中统一声明和初始化共享变量的最佳实践,可以有效地避免此类问题。利用IDE的警告功能,并区分方法重写与变量遮蔽,是编写健壮、可维护Java代码的关键。
以上就是Java继承中的变量遮蔽:深入理解与解决方案的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 //m.sbmmt.com/ All Rights Reserved | php.cn | 湘ICP备2023035733号