当前位置: 首页 > news >正文

零基础 “入坑” Java--- 十一、多态

文章目录

  • 一、何为多态
  • 二、多态实现条件
  • 三、向上转型
  • 四、方法重写
  • 五、动态绑定和静态绑定
  • 六、向下转型(instanceof)
  • 七、多态
  • 八、多态优缺点及注意事项
    • 1.优点
    • 2.缺点
    • 3.注意事项

在前两个章节中,我们已经对封装和继承有了深入的了解,本章节我们就来学习面向对象的最后一个重要思想——多态!

一、何为多态

多态,通俗来讲就是多种形态,具体点就是当不同的对象去做同一件事时产生的不同的效果。

比如:当我们想要打印照片时,需要使用到打印机,当使用彩色打印机时,打印出的照片就是彩色的;使用黑白打印机时,打印的照片就是黑白的。

二、多态实现条件

多态是一种非常抽象的思想,要想真正理解多态以及多态的实现条件,我们需要先知道:

1.向上转型
2.子类和父类存在同名的重写(覆盖)方法(即子类对父类中的方法进行重写)。
3.通过父类引用,调用子类对象重写的方法。

满足以上这三点,可以说明发生了动态绑定:

什么是动态绑定,什么是静态绑定?

理解了以上这些,我们就能了解多态。

三、向上转型

语法格式:

父类类型 对象名 = new 子类类型();

class Animal {public String name;public int age;public Animal(String name) {this.name = name;}public void eat() {System.out.println(this.name + "吃饭");}
}
class Dog extends Animal {public Dog(String name) {super(name);}public void wang() {System.out.println("汪汪");}
}

我们定义一个动物类,一个狗类,并让狗类继承动物类,向上转型即:父类的引用,引用了子类对象;简单来说,就是把子类对象给父类。需要注意:向上转型是从小范围向大范围进行转换。

public class Test {public static void main(String[] args) {//方法一Dog dog = new Dog("小狗1");Animal animal = dog;//方法二(简写)Animal animal1 = new Dog("小狗2");}
}

在上面这段代码中,因为狗类继承了动物类,就发生了向上转型。向上转型不只有一种形式:

public class Test {public static void main(String[] args) {Dog dog = new Dog("小狗1");test(dog);}public static void test(Animal animal1) {System.out.println("向上转型");}
}

在这段代码中也发生了向上转型,test方法的参数为Animal,但是当我们在main方法中调用test方法时,传递的参数为dog,此时代码也能正常运行。

public class Test {public static void main(String[] args) {Animal animal = test();}public static Animal test() {System.out.println("向上转型");return new Dog("小狗");}
}

上面这段代码中,也发生了向上转型,我们将test方法的返回值类型定义为Animal类,返回一个Dog类,并用Animal类接收。

总结一下:

可以通过三种方式发生向上转型:
1.直接赋值
2.方法传参
3.返回值

在代码中使用向上转型可以使代码的实现更简单灵活,但是使用向上转型也会受到局限:

public class Test {public static void main(String[] args) {Animal animal = new Dog("小狗");animal.wang();}
}

在这段代码中,我们使用向上转型实例化了一个对象,当我们想要通过实例化出的对象调用狗类中的方法时,会发现没办法正常调用:
在这里插入图片描述
这就是向上转型的缺点,即:不能调用子类中独有的方法。

四、方法重写

我们增加一个狗类中的成员方法,并尝试通过向上转型实例化出的对象去调用:

class Dog extends Animal {public Dog(String name) {super(name);}public void wang() {System.out.println("汪汪");}public void eat() {System.out.println(this.name + "吃狗粮");}
}
public class Test {public static void main(String[] args) {Animal animal = new Dog("小狗");animal.eat();}
}

此时代码可以正常运行,运行结果为:“小狗吃狗粮”。我们刚才明明学习过,发生向上转型时,无法调用子类中独有的方法,此时,却又可以调用了,这是为什么呢?

在这里插入图片描述
我们在Animal类中也存在eat方法,此时子类中的eat方法就相当于对父类的同名方法进行重写,所以,才可以调用eat方法。

重写:也称为覆盖,是子类对父类中 非静态、非private修饰、非final修饰、非构造方法等 的实现过程进行重新编写。重写可以根据子类需要,定义专属于自己的行为。

重写时,需要确保重写的方法:1.方法名相同;2.参数列表相同(参数的类型、顺序、个数等都必须相同);3.返回值相同。满足以上这三点,才会发生方法的重写。

对于重写的方法,可以使用@Override注释显式指定,当我们添加了这行注释之后,编译器就可以帮助我们进行校验:
在这里插入图片描述
在上一章节中,重写toString方法就用到了这一点。


重写注意事项:

1.被private修饰的方法不能进行重写。
2.被static修饰的方法不能进行重写。
3.被final修饰的方法不能进行重写(被final修饰的方法也叫密封方法)。
4.构造方法不能进行重写。
5.方法的返回值可以不同,但必须为父子类关系:

在这里插入图片描述
6.子类中重写的方法的访问权限 不能比 父类中被重写的方法的访问权限低,如:若父类中方法被public修饰,那么当子类重写父类的该方法时,访问修饰限定符就只能为public(访问权限:private < 默认权限 < protected < public)。

重写和重载:
在这里插入图片描述

五、动态绑定和静态绑定

在这里插入图片描述
如果在子类中没有重写eat方法,那么将会正常调用父类自己的eat方法,但是当子类中重写了eat方法之后,就会调用子类中重写的方法,这就是动态绑定。

动态绑定/运行时绑定:需要等到程序运行时,才能确定调用哪个类的方法(可能与编译时认为调用的类的方法不同),如:方法的重写。


静态绑定/编译时绑定:在编译的时候,根据用户传递的参数的类型即可确定调用哪个方法,如:方法的重载。

六、向下转型(instanceof)

我们重新来编写一段代码:

class Animal {public String name;public int age;public Animal(String name) {this.name = name;}
}
class Dog extends Animal {public Dog(String name) {super(name);}public void wang() {System.out.println("汪汪");}
}
class Bird extends Animal {public Bird(String name) {super(name);}public void fly() {System.out.println("起飞");}
}

我们已经知道,发生向上转型时,无法调用子类中独有的方法:

    public static void main(String[] args) {Animal animal = new Dog("小狗");animal.wang(); //error}

但是,如果我们执意要调用,就可以通过强制类型转换来使用:

    public static void main(String[] args) {Animal animal = new Dog("小狗");//向下转型Dog dog = (Dog)animal;dog.wang();}

此时,我们就通过向下转型调用了子类中的方法,但是向下转型很不安全,比如:

    public static void main(String[] args) {Animal animal = new Dog("小狗");//向下转型Dog dog = (Dog)animal;dog.wang();Animal animal1 = new Bird("小鸟");//向下转型Dog dog1 = (Dog)animal1;dog1.wang();}

运行这段代码:
在这里插入图片描述
第一次实例化的对象:animal,可以正常使用;第二次实例化出的对象:animal1,会因为强转的类型不匹配而编译异常;但是在编写代码过程中,编译器并没有通过红色波浪线提示错误。因此,在向下转型时,需要进行检查!!

此时,instanceof关键字就可以帮助我们完成检查这个操作:

    public static void main(String[] args) {Animal animal = new Dog("小狗");//向下转型if (animal instanceof Dog) {Dog dog = (Dog) animal;dog.wang();}Animal animal1 = new Bird("小狗");//向下转型if (animal1 instanceof Dog) {Dog dog1 = (Dog) animal1;dog1.wang();} else {System.out.println("不安全");}}

instanceof表达式的返回值为布尔类型,如果表达式结果为true,则代表可以安全转换。

七、多态

class Shape {public void draw() {System.out.println("画画");}
}
class Rectangle extends Shape {@Overridepublic void draw() {System.out.println("矩形");}
}
class Cycle extends Shape {@Overridepublic void draw() {System.out.println("圆形");}
}
class Flower extends Shape {@Overridepublic void draw() {System.out.println("花花");}
}
public class Test {public static void drawMap(Shape shape) {shape.draw();}public static void main(String[] args) {Rectangle rectangle = new Rectangle();Cycle cycle = new Cycle();Flower flower = new Flower();drawMap(rectangle);drawMap(cycle);drawMap(flower);}
}

在上面这段代码中,我们就充分利用了多态的思想:
在这里插入图片描述
虽然在drawMap方法中我们只使用了 一个引用 调用了一个方法,但是当我们传递的参数不同时,得出的结果也不同。这是因为引用的对象不同:当我们引用的对象不同,但调用同一个重写的方法,就会产生不一样的表现行为,这种思想就叫做多态!!!

在上面这个例子中,也发生了动态绑定,因此我们说动态绑定是多态的基础。

八、多态优缺点及注意事项

1.优点

  • 可以降低代码的圈复杂度,避免使用大量的if-else语句,使代码的可读性更高。(圈复杂度是一种描述代码复杂程度的方式。)
  • 代码的可扩展能力更强。

2.缺点

  • 使用多态会导致代码的运行效率降低。

3.注意事项

  • 成员变量没有多态性
  • 构造方法没有多态性
  • 应避免在构造方法中调用重写的方法,这会导致程序的运行结果与我们想要的结果不同,如:
class B {public B() {func();}public void func() {System.out.println("B.func()");}
}
class D extends B {private int num = 1;@Overridepublic void func() {System.out.println("D.func() " + num);}
}
public class Test2 {public static void main(String[] args) {D d = new D(); //D.func() 0}
}

在这段代码中,我们实例化一个D类型的对象,在子类中需要先帮助父类完成构造,因此调用父类B构造方法,B的构造方法中调用了func方法,此时会产生动态绑定,调用D中的func;但是此时D还没开始构造,因此num为未初始化状态,值为0。


Ending。

http://www.dtcms.com/a/279718.html

相关文章:

  • Spring Boot + Vue2 实现腾讯云 COS 文件上传:从零搭建分片上传系统
  • 并发编程核心概念详解:进程、线程与协程的本质与差异
  • 解锁HTTP:从理论到实战的奇妙之旅
  • Windows系统使用docker部署项目(有网与离线)
  • LeetCode--45.跳跃游戏 II
  • 破局与重构:文心大模型开源的产业变革密码
  • 北京饮马河科技公司 Java 实习面经
  • vscode 打开项目时候,有部分外部依赖包找不到定义或者声明,但是能使用cmake正常编译并且运行
  • C#——数据与变量
  • 软件构件组装三层次体系:定制、集成与扩展的深度解析
  • Product Hunt 每日产品热榜 | 2025-07-14
  • 【氮化镓】非辐射复合载流子诱导的瞬态缺陷机制
  • 实现dify关于python安装包功能的实现
  • java进阶(三):单例、工厂、模版方法与代理模式详解
  • 【25-cv-7832】TME携瓢虫少女动漫发起商标+版权维权案,近百家店铺被冻!!
  • Spring Boot 实现图片防盗链:Referer 校验与 Token 签名校验完整指南
  • 【html基本界面】
  • 李沐动手学深度学习Pytorch-v2笔记【08线性回归+基础优化算法】2
  • FreeRTOS之链表操作相关接口
  • 【深度学习框架终极PK】TensorFlow/PyTorch/MindSpore深度解析!选对框架效率翻倍
  • xss-lab靶场通关
  • 多租户云环境下的隔离性保障:虚拟化、容器、安全组如何协同防护?
  • python高阶调试技巧,替代pring
  • HDMI接口定义(二)
  • 网络编程7.12
  • 【elasticsearch9.0】【kibana】Docker安装
  • Java从入门到精通!第五天(面向对象(二))
  • JAR 包冲突排雷指南:原理、现象与 Maven 一站式解决
  • 深度解读virtio:Linux IO虚拟化核心机制
  • 评论设计开发