杰瑞科技汇

java子类对象赋值给父类对象

向上转型

在 Java 中,将一个子类对象赋值给其父类引用变量,这个过程被称为 向上转型

java子类对象赋值给父类对象-图1
(图片来源网络,侵删)

语法形式:

// 父类类型 引用变量 = new 子类构造器();
Parent parentRef = new Child();

这里的 Parent 是父类,Child 是子类。parentRef 是一个类型为 Parent 的引用变量,但它指向了一个在堆内存中创建的 Child 对象。


一个简单的代码示例

为了更好地理解,我们来看一个具体的例子。

定义父类和子类

// 父类: Animal.java
class Animal {
    public void eat() {
        System.out.println("Animal is eating.");
    }
    public void sleep() {
        System.out.println("Animal is sleeping.");
    }
}
// 子类: Dog.java
class Dog extends Animal {
    // 子类继承了父类的所有非私有成员
    // 重写父类的 eat() 方法
    @Override
    public void eat() {
        System.out.println("Dog is eating bone.");
    }
    // 子类特有的方法
    public void bark() {
        System.out.println("Dog is barking.");
    }
}

执行向上转型

// Main.java
public class Main {
    public static void main(String[] args) {
        // 1. 创建一个子类对象
        Dog myDog = new Dog();
        // 2. 向上转型:将子类对象赋值给父类引用
        Animal myAnimal = myDog;
        System.out.println("--- 通过父类引用调用方法 ---");
        // 3. 通过父类引用来调用方法
        myAnimal.eat(); // 调用的是子类重写的方法
        myAnimal.sleep(); // 调用的是继承自父类的方法
        // 4. 尝试调用子类特有的方法
        // myAnimal.bark(); // 这行代码会编译错误!
    }
}

运行结果

--- 通过父类引用调用方法 ---
Dog is eating bone.
Animal is sleeping.

关键点解析

编译时类型 vs. 运行时类型

理解向上转型的关键在于区分两个概念:

java子类对象赋值给父类对象-图2
(图片来源网络,侵删)
  • 编译时类型: 由声明变量时使用的类型决定,在 Animal myAnimal = new Dog(); 这行代码中,myAnimal 的编译时类型是 Animal
  • 运行时类型: 由 new 关键字创建的对象的实际类型决定,在这行代码中,myAnimal 的运行时类型是 Dog

Java 的方法调用机制(动态绑定/后期绑定): 当通过一个引用变量调用一个方法时,JVM 会查看该变量的运行时类型,并调用该运行时类型中定义的方法,如果运行时类型中没有找到该方法,则会去其父类中查找,直到找到为止。

这就是为什么 myAnimal.eat() 会输出 "Dog is eating bone." 的原因,虽然编译器只知道 myAnimalAnimal 类型,但在运行时,JVM 发现它实际指向的是 Dog 对象,于是就调用了 Dog 类中重写的 eat() 方法。

“能调用什么?”——访问权限的限制

向上转型后,父类引用变量 myAnimal 只能访问在其编译时类型(Animal中定义的成员(方法和字段)。

  • 可以访问:

    java子类对象赋值给父类对象-图3
    (图片来源网络,侵删)
    • 父类中定义的方法(如 sleep())。
    • 子类重写了父类的方法(如 eat(),调用的是子类的版本)。
  • 无法访问:

    • 子类特有的方法(如 bark())。
    • 子类新增的字段。

如果你尝试调用子类特有的方法,编译器会直接报错,因为它在 Animal 类的“蓝图”里找不到 bark() 方法。

// 编译错误: 找不到符号
myAnimal.bark(); 

“为什么需要向上转型?”——实现多态

向上转型的主要目的就是为了实现 多态

多态: 指的是“一种行为,多种形态”,就是同一个接口(或父类),使用不同的实例(子类)而执行不同操作。

多态的好处:

  1. 提高代码的灵活性和可扩展性: 你可以编写一个方法,它接收父类类型的参数,这样任何子类的对象都可以传递给这个方法,而不需要为每个子类都写一个重载版本。

    public static void performAction(Animal animal) {
        animal.eat(); // 这里可以接收 Dog, Cat, Bird... 等任何 Animal 的子类
    }
    Dog dog = new Dog();
    Cat cat = new Cat();
    performAction(dog); // 输出: Dog is eating bone.
    performAction(cat); // 假设 Cat 也重写了 eat()
  2. 简化代码,减少重复: 不用为每个子类都写一套逻辑,只需关注父类的通用行为即可。


与之相对的概念:向下转型

既然有“向上转型”,自然就有“向下转型”,即把父类引用再转回子类引用。

语法形式:

// 子类类型 子类引用 = (子类类型) 父类引用;
Dog myDogAgain = (Dog) myAnimal;

向下转型的前提: 父类引用变量必须指向一个真实的子类对象,如果它本身就是 new Parent() 创建的,那么向下转型就会在运行时抛出 ClassCastException(类型转换异常)。

安全地进行向下转型: 使用 instanceof 运算符进行判断。

if (myAnimal instanceof Dog) {
    // 只有在确认是 Dog 类型后,才进行强制转换
    Dog myDogAgain = (Dog) myAnimal;
    myDogAgain.bark(); // 现在可以安全地调用子类特有方法了
} else {
    System.out.println("myAnimal is not a Dog object.");
}

特性 描述
名称 向上转型
语法 Parent p = new Child();
本质 将子类对象看作是其父类类型的一个实例。
编译时类型 父类类型 (Parent),决定了能调用哪些声明的方法。
运行时类型 子类类型 (Child),决定了实际调用的是哪个实现的方法(动态绑定)。
访问权限 只能访问父类中定义的方法和字段,如果子类重写了父类方法,则调用子类的版本。无法访问子类特有的成员
目的 实现多态,提高代码的灵活性、可扩展性和复用性。
反向操作 向下转型,需要使用 instanceof 进行安全检查,否则可能抛出 ClassCastException

掌握向上转型是理解 Java 面向对象编程,特别是多态、设计模式(如策略模式、工厂模式等)的关键一步。

分享:
扫描分享到社交APP
上一篇
下一篇