Java引用类型转换:声明父类引用,赋实例为子类

在写码的过程中,经常出现比如

// 声明一个父类引用,实际上将它赋给了一个子类对象
List<String> myList = new ArrayList<>();

这样的情况,这时就会好奇,这样的多态声明具体规则是怎样的?这次总结一下:

在Java中,引用可以进行类型转换: 我们可以将一个子类引用转换为其父类引用,这叫做向上转换(upcast)或者宽松转换。所以我们可以声明一个父类引用,并将其赋给一个子类的实例。这是多态性的一个重要概念,允许我们以一种通用的方式操作不同子类的对象,而不需要知道具体的子类类型,比如:

class Animal {
    void makeSound() {
        System.out.println("Animal makes a sound");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("Dog barks");
    }
}

class Cat extends Animal {
    @Override
    void makeSound() {
        System.out.println("Cat meows");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal1 = new Dog(); // 子类实例赋给父类引用
        Animal animal2 = new Cat(); // 子类实例赋给父类引用

        animal1.makeSound(); // 调用Dog类的makeSound方法
        animal2.makeSound(); // 调用Cat类的makeSound方法
    }
}

在上面的示例中,DogCat 类都是 Animal 类的子类。然后,我们声明了两个 Animal 类型的引用变量 animal1animal2,并将它们分别赋值为 DogCat 类的实例,然后分别调用两个animal的makeSound,运行正常,没有问题。

这个时候会想,如果我在子类比如dog中新加一个方法叫bark,新加一个成员变量叫breed,那么我声明的父类引用animal1, 可以正确使用这两个成员方法和变量吗?

答案是不可以,父类引用 animal1 不能直接访问这两个新添加的成员,因为父类 Animal 没有定义这些成员。只有子类 Dog 才能访问它们:

class Animal {
    void makeSound() {
        System.out.println("Animal makes a sound");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("Dog barks");
    }

    void bark() {
        System.out.println("Dog is barking");
    }

    String breed; // 新的成员变量 breed
}

public class Main {
    public static void main(String[] args) {
        Animal animal1 = new Dog();
        
        // 下面两行是不合法的,因为父类引用不能访问子类特有的方法和成员变量
        // animal1.bark(); // 错误,父类引用不能调用子类特有的方法
        // animal1.breed = "Labrador"; // 错误,父类引用不能访问子类特有的成员变量

        // 但是可以调用从父类继承的方法
        animal1.makeSound(); // 合法,会调用子类 Dog 的 makeSound 方法
    }
}

如上所示,尝试使用 animal1 来调用 bark 方法和访问 breed 变量将会导致编译错误,因为这些是子类 Dog 特有的成员。只有在将 animal1 强制转换为 Dog 类型之后,才能访问这些子类特有的成员。

if (animal1 instanceof Dog) {
    Dog dog = (Dog) animal1; // 强制类型转换为 Dog 类型
    dog.bark(); // 现在可以访问 bark 方法
    dog.breed = "Labrador"; // 现在可以访问 breed 成员变量
}

所以,同理

当我们声明一个 List<String> 引用并将其实际赋值为 ArrayList<String> 时,我们可以调用 List 接口中定义的方法,但不能直接调用 ArrayList 特有的方法。这是因为引用类型确定了我们可以调用的方法,而不是实际对象的类型。

public class Main {
    public static void main(String[] args) {
        List<String> myList = new ArrayList<>();

        // 添加元素 - 这是List接口的方法
        myList.add("Apple");
        myList.add("Banana");
        myList.add("Cherry");

        // 获取元素 - 这是List接口的方法
        String fruit = myList.get(0);
        System.out.println("First fruit: " + fruit);

        // 不能调用ArrayList特有的方法,比如ensureCapacity
        // myList.ensureCapacity(20); // 这将导致编译错误

        // 若要调用ArrayList特有的方法,需要进行强制类型转换
        if (myList instanceof ArrayList) {
            ArrayList<String> arrayList = (ArrayList<String>) myList;
            arrayList.ensureCapacity(20); // 这是ArrayList的方法
        }
    }
}

那么,声明List而不是ArrayList的好处是什么呢?

1. 更通用的代码:声明 List 可以编写更通用的代码,不会依赖于特定的 List 实现。这使得代码更具灵活性,这样当我们以后想换到其他 List 实现,如 LinkedList,只需要更改最上面一行ArrayList换成LinkedList就行了,而不需要更改大部分代码。

2. 接口隔离原则:使用 List 接口符合接口隔离原则,这是面向对象设计原则之一。它强调了客户端代码(使用 List 的代码)不应该依赖于不需要的方法。通过声明 List,只公开了 List 接口定义的方法,而不是 ArrayList 特有的方法,有助于保持代码的干净和可维护。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值