Java | 浅谈多态中的向上转型与向下转型

简介: Java | 浅谈多态中的向上转型与向下转型

🌳向上转型

📕概念明细

【原理】:实际就是创建一个子类对象,将其当成父类对象来使用.

语法格式:父类类型 对象名 = new 子类类型()

Animal animal = new Cat("元宝",2);  
//animal是父类类型,但可以引用一个子类对象,因为是从小范围向大范围的转换
  • 以下是定义的父类Animal类它的两个继承类Cat类和Dog类,分别重写了Animal类的eat()方法
class Animal{
     String name;
     int age;
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void eat(){
        System.out.println(age + "岁的" + name + "正在吃东西");
    }
}
class Cat extends Animal {
    public Cat(String name, int age) {
        super(name, age);
    }
    @Override
    public void eat() {
        System.out.println(age + "岁的" + name + "正在吃猫粮");
    }
}
class Dog extends Animal {
    public Dog(String name, int age) {
        super(name, age);
    }
    @Override
    public void eat() {
        System.out.println(age + "岁的" + name + "正在吃狗粮");
    }
}

image.png

💪使用场景1:直接赋值

  • 以下这种就是直接赋值类型的向上转型,将子类对象的引用给到父类对象。然后再将这个对象作为参数传递进去,就可以根据不同引用调用不同的行为
public class Test1 {
    public static void eat(Animal animal)
    {
        animal.eat();
    }
    public static void main(String[] args) {
        Animal animal1 = new Animal("动物",3);
        Animal animal2 = new Cat("加菲猫",2);
        Animal animal3 = new Dog("哈士奇",1);
        eat(animal1);
        eat(animal2);
        eat(animal3);
    }
}

image.png

💪使用场景2:方法传参

  • 第二种方法传参很简单,我们连对象都不需要创建,只需要直接将三种不同对象的引用作为实参传递给到eat()中的形参接受,就可以产生【向上转型】
public class Test1 {
    public static void eat(Animal animal)
    {
        animal.eat();
    }
    public static void main(String[] args) {
        eat(new Animal("动物",3));
        eat(new Cat("加菲猫",2));
        eat(new Dog("哈士奇",1));
    }
}

💪使用场景3:方法返回

  • 第三种便是在一个方法中将不同对象的引用进行返回,然后传递给到父类对象,也可以产生向上转型
public static void eat(Animal animal)
{
    animal.eat();
}
  • 稍微讲一下这个方法,可以看到形参是String字符类型,在方法中通过判断外界传入进来的字符串然后对应地进行返回
  • 若是“猫”就返Cat对象的引用
  • 若是“狗”就返Dog对象的引用
  • 若是其他就返null
public static Animal BuyAnimal(String var)
{
    if(var.equals("猫")){
        return new Cat("加菲猫",2);
    }else if(var.equals("狗")){
        return new Dog("哈士奇",1);
    }else{
        return null;
    }
}
Animal animal1 = BuyAnimal("猫");
eat(animal1);
Animal animal2 = BuyAnimal("狗");
eat(animal2);
Animal animal3 = BuyAnimal("兔");
eat(animal3);
  • 然后可以看到,前两个传入的是【猫】和【狗】,因此执行的便是不同对象的行为,但是在最后传入了【兔】,因此在BuyAnimal()方法中会return null
  • 这就使得传入eat()方法中去调用相关对象的行为时因为不存在这个对象的引用,所以就会产生空指针异常

image.png

上述便是向上转型的三种场景。学会辨别和使用即可

💪向上转型的优缺点

【优点】:让代码实现更简单灵活,这个从上面的三个不同场景就可以看得出来 【缺点】:不能调用到子类特有的方法👇

  • 比如说我在Dog类中写了一个它自己独有的方法lookdoor()
class Dog extends Animal {
    public Dog(String name, int age) {
        super(name, age);
    }
    @Override
    public void eat() {
        System.out.println(age + "岁的" + name + "正在吃狗粮");
    }
    public void lookdoor(){
        System.out.println(name + "在看门");
    }
}
  • 但是可以看到,我在通过向上转型之后通过父类对象接受子类引用但是调用不到子类中特有的方法,这其实就是向上转型的缺陷image.png

【注意事项】

  • 上转型对象不可以访问子类新增的成员变量和子类自己新增的方法,因为这是子类独有的,而父类中没有
  • 上转型对象可以访问子类继承的方法或者是子类重写的方法,这个时候==当上转型对象去调用这个方法时,一定是调用了子类重写后的方法==,这就是我们前面在讲继承的时候所提到的方法重写
  • 不可以将父类创建的对象的引用赋值给子类声明的对象,也就是下面的这两句代码,这很明显和我们的上转型对象相反的,我们是将子类对象给到父类的引用,但这是将父类的引用给到子类的对象,完全就是颠倒黑白【就和猫是动物,动物却不是猫🐱一个道理】

🌳向下转型

接下去我们来讲讲向下转型,这种类型的转化不太安全,如果没有特殊需求,不建议使用

🔩向下转型解决【调用子类独有方法】

  • 继续上面的谈到的在向上转型之后无法调用子类独有的方法这个问题,其实向下转型就可以解决,代码如下
Animal animal1 = new Dog("哈士奇",1);
animal1.eat();
//animal1.lookdoor();
Dog dog = (Dog) animal1;
dog.lookdoor();
  • 可以看到,也就是将父类的对象animal强转成为Dog狗类的对象,这样的话其实就可以去调用子类中特有的方法了

image.png

🔩向下转型的缺陷

那为什么说向下转型不安全呢,因为它存在安全隐患

Animal animal1 = new Cat("加菲猫",2);
animal1.eat();
//animal1.lookdoor();
Dog dog = (Dog) animal1;
dog.lookdoor();
  • 可以看到我将原先的Dog狗类变为了Cat猫类,此时animal就得到了猫类对象的引用,但是在下面可以看到如果将这个animal强转为Dog狗类的对象其实就会出问题了
  • 看到编译器报出了ClassCastException —— 类型转换异常

image.png

🔩向下转型的优化

👉向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。 👉Java中为了提高向下转型的安全性,引入了 instanceof ,如果该表达式为true,则可以安全转换

  • 可以看到,在进行强转之前我通过instanceof这个关键字进行了一下判断,看看animal1是否获取到了Dog类的引用,若是才可以进行强制类型转化,若不是的话就不会进行任何操作
Animal animal1 = new Cat("加菲猫",2);
animal1.eat();
//animal1.lookdoor();
if(animal1 instanceof Dog){   //判断一下animal1是否获取到了Dog类的引用
  Dog dog = (Dog) animal1;
  dog.lookdoor();
}

image.png

如果对instance关键字感兴趣的可以看看官方的文档 ——>instanceof关键字

image.png


🌳再度对比二者【碎碎念】

可能在上面这一系列说来有点难分辨,我们再来对比看看

  • 其实你可以这么去想Animal animal1 = new Cat可以看成是猫归属于一个动物类,那猫一定是属于动物的
  • 但是看到Dog dog = (Dog) animal1其实就要去思考把动物归属于狗,这其实是说不通的,难道只要是动物就一定是狗吗?==那可不一定,动物可多的是==

image.png

🌳总结与提炼

来总结一下本文所学到的内容

  • 在本文中我们讲到了多态中的【向上转型】与【向下转型】
  • 首先是说到了向上转型,介绍了它会出现的三种场景,也分析了它的优缺点,知道了在向下转型之后无法调用子类特有的方法
  • 但是在向下转型中,我们解决了这个问题,通过对父类对象进行一个强转,就可以调用到子类当中的方法,不过可以看到这种做法不太安全,若是一开始父类对象接受了一个子类的引用,但是在强转的时候转化为了另外一个子类,就会造成类型转换的问题
  • 于是后面对方法进行了修正,在前面加上了instanceof关键字进行一个判断,只有父类接收到了这个子类的引用,才可以强转为这个子类的对象
相关文章
|
2月前
|
存储 Java 测试技术
Java零基础-多态详解
【10月更文挑战第10天】Java零基础教学篇,手把手实践教学!
36 4
|
2月前
|
Java 编译器 程序员
Java多态背后的秘密:动态绑定如何工作?
本文介绍了Java中多态的实现原理,通过动态绑定和虚拟方法表,使得父类引用可以调用子类的方法,增强了代码的灵活性和可维护性。文中通过具体示例详细解析了多态的工作机制。
78 4
|
3月前
|
Java 编译器
封装,继承,多态【Java面向对象知识回顾①】
本文回顾了Java面向对象编程的三大特性:封装、继承和多态。封装通过将数据和方法结合在类中并隐藏实现细节来保护对象状态,继承允许新类扩展现有类的功能,而多态则允许对象在不同情况下表现出不同的行为,这些特性共同提高了代码的复用性、扩展性和灵活性。
封装,继承,多态【Java面向对象知识回顾①】
|
2月前
|
Java
java继承和多态详解
java继承和多态详解
52 5
|
2月前
|
存储 Java 测试技术
Java零基础-多态详解
【10月更文挑战第1天】Java零基础教学篇,手把手实践教学!
31 1
|
2月前
|
安全 Java 编译器
【一步一步了解Java系列】:重磅多态
【一步一步了解Java系列】:重磅多态
25 3
|
2月前
|
安全 Java
【Java】向上转型和向下转型
【Java】向上转型和向下转型
32 0
|
2天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
4天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
4天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。