在 Java 面向对象编程中,继承是核心特性之一,而变量隐藏(Variable Hiding)方法重写(Method Overriding) 是继承体系中两个极易混淆的概念。二者虽都涉及子类与父类的同名成员,但本质、访问规则及体现的特性完全不同,尤其在多态性的支持上存在根本差异。本文将从定义、规则、代码示例到核心区别进行全面解析,帮助开发者彻底厘清二者的边界。

一、概念定义:本质与核心特点

要理解两者的差异,首先需明确其定义和本质,这是后续分析的基础。

概念

定义描述

核心特点

变量隐藏

当子类中定义了与父类同名的成员变量(包括实例变量和静态变量)时,子类的变量会 “隐藏” 父类的同名变量。注意:父类变量并未被删除,只是子类中无法直接通过变量名访问父类变量。

1. 无多态性,访问结果在编译时即可确定;2. 子类与父类的同名变量是两个独立的变量,占用不同内存空间。

方法重写

子类中定义了与父类方法名、参数列表、返回值类型完全一致的实例方法(返回值可支持协变,即子类返回值为父类返回值的子类),此时子类方法会 “覆盖” 父类方法的逻辑。

1. 支持多态性,调用结果在运行时动态确定(动态绑定);2. 子类方法替换父类方法的逻辑,本质是对父类方法的扩展或修改。

二、访问规则:编译时与运行时的差异

访问规则是区分变量隐藏和方法重写的关键,二者的核心差异体现在 “何时确定访问的成员”。

1. 变量隐藏:编译时绑定,看 “引用类型”

变量隐藏的访问规则可总结为 “编译看左边,运行也看左边”—— 即通过对象访问隐藏的变量时,仅取决于引用变量的编译时类型 (声明时的类型),与对象的实际运行时类型无关。

  • 若引用类型为父类:访问父类的变量;

  • 若引用类型为子类:访问子类的变量;

  • 若需通过父类引用访问子类隐藏的变量,必须进行强制类型转换(将父类引用转为子类引用)。

2. 方法重写:运行时绑定,看 “对象类型”

方法重写的访问规则可总结为 “编译看左边,运行看右边”—— 编译阶段仅检查引用类型是否包含该方法(保证语法合法性),运行阶段则根据对象的实际运行时类型 (new 关键字创建的类型)确定调用的方法。

  • 若对象实际类型为子类:调用子类重写后的方法;

  • 若对象实际类型为父类:调用父类原方法;

  • 无需强制转换,多态性会自动生效。

三、代码示例:直观对比两种场景

为了更清晰地展示差异,我们通过一个统一的场景(父类Parent与子类Child),分别演示变量隐藏和方法重写的表现。

场景准备:父类与子类的定义

// 父类
class Parent {
    // 父类成员变量
    String info = "Parent's Variable";

    // 父类实例方法
    void printInfo() {
        System.out.println("Parent's Method: " + info);
    }
}

// 子类(继承自Parent)

class Child extends Parent {
    // 子类成员变量:隐藏父类的info变量
    String info = "Child's Variable";

    // 子类方法:重写父类的printInfo()方法
    @Override
    void printInfo() {
        System.out.println("Child's Method: " + info);
    }
}

测试代码:父类引用指向子类对象

在测试中,我们使用 “父类引用指向子类对象”(Parent obj = new Child()),这是体现多态性的典型场景,也是区分两者的关键场景。

public class TestHideAndOverride {
    public static void main(String[] args) {
        // 父类引用指向子类对象(多态场景)
        Parent obj = new Child();

        // 1. 访问变量:遵循变量隐藏规则
        System.out.println("访问变量:" + obj.info);
        // 输出结果:访问变量:Parent's Variable(看引用类型Parent)

        // 2. 调用方法:遵循方法重写规则
        System.out.print("调用方法:");
        obj.printInfo();
        // 输出结果:调用方法:Child's Method: Child's Variable(看对象类型Child)

        // 3. 强制转换:通过父类引用访问子类隐藏的变量
        System.out.println("强制转换后访问变量:" + ((Child) obj).info);
        // 输出结果:强制转换后访问变量:Child's Variable(转为子类引用)

    }

}

最终输出结果

访问变量:Parent's Variable
调用方法:Child's Method: Child's Variable
强制转换后访问变量:Child's Variable

四、核心区别总结:一张表厘清边界

通过以上分析,我们可将变量隐藏与方法重写的差异归纳为下表,便于快速查阅和记忆:

对比维度

变量隐藏(Variable Hiding)

方法重写(Method Overriding)

多态性支持

❌ 不支持(编译时确定)

✅ 支持(运行时动态绑定)

访问依据

引用的编译时类型(声明类型)

对象的运行时类型(实际创建的类型)

语法标识

无特殊标识(直接定义同名变量)

推荐使用@Override注解(校验重写合法性)

父类引用访问

仅访问父类变量(需强转才能访问子类变量)

直接访问子类重写方法(无需强转)

成员类型限制

实例变量、静态变量均可隐藏

仅实例方法可重写(静态方法无重写,仅隐藏)

本质关系

子类与父类变量是独立变量(不同内存)

子类方法覆盖父类方法(同一方法逻辑替换)

访问权限影响

变量权限不影响隐藏(如父类 public,子类 private 仍可隐藏)

子类方法权限不能低于父类(如父类 public,子类不能为 private)

五、开发注意事项:避免踩坑

在实际开发中,合理使用方法重写、避免滥用变量隐藏,是写出清晰、可维护代码的关键。

1. 变量隐藏:尽量避免

  • 可读性差:如示例中obj.info的结果可能不符合直觉(开发者可能误以为访问子类变量);

  • 内存浪费:子类与父类的同名变量独立存在,占用额外内存;

  • 替代方案:若需修改父类变量的值,可通过父类的setter方法,而非定义同名变量。

2. 方法重写:遵循规范

  • 必加@Override注解:该注解可让编译器校验重写的合法性(如方法名、参数列表是否与父类一致),避免因拼写错误导致 “伪重写”;

  • 遵循 “里氏替换原则”:子类方法应与父类方法的逻辑语义一致,避免破坏继承体系的一致性;

  • 注意返回值协变:Java 5 + 支持返回值协变,即子类方法返回值可改为父类返回值的子类(如父类返回Object,子类可返回String),但需保证类型兼容。

3. 静态成员的特殊性

  • 静态变量和静态方法无重写:静态成员属于 “类”,而非 “对象”,其访问规则与变量隐藏一致(看引用类型);

  • 避免混淆:若子类定义了与父类同名的静态方法,本质是 “静态方法隐藏”,而非重写,不支持多态。

六、总结

变量隐藏和方法重写是 Java 继承的核心概念,二者的根本差异在于是否支持多态性

  • 变量隐藏是 “编译时绑定”,仅看引用类型,无多态;

  • 方法重写是 “运行时绑定”,看对象类型,体现多态。

理解这一差异,不仅能避免开发中的常见错误,更是掌握 Java 面向对象思想(尤其是多态)的关键。在实际开发中,应充分利用方法重写实现代码复用与扩展,同时尽量避免变量隐藏,保持代码的清晰性和可维护性。