当前位置: 首页 > 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.lryc.cn/news/588202.html

相关文章:

  • 详解同步、异步、阻塞、非阻塞
  • 12.4 Hinton与Jeff Dean突破之作:稀疏门控MoE如何用1%计算量训练万亿参数模型?
  • UM680A模块接地与散热和封装推荐设计
  • MIPI DSI(三) MIPI DSI 物理层和 D-PHY
  • 2D和3D激光slam的点云去运动畸变
  • SLAM 前端
  • Doll靶机渗透
  • openEuler系统PCIE降速方法简介
  • 基于YOLOV8的烟火检测报警系统的设计与实现【全网独一、报警声音机制、实时画面、系统交互、日志记录】
  • SSM框架学习——day1
  • MySQL窗口函数详讲
  • VUE3 添加长按手势
  • Web 前端面试
  • C++-linux 7.文件IO(一)系统调用
  • Day34 Java方法05 可变参数
  • OSPF高级特性之GR
  • 现有医疗AI记忆、规划与工具使用的创新路径分析
  • 【Java笔记】七大排序
  • Android Studio C++/JNI/Kotlin 示例 二
  • 清除 Android 手机 SIM 卡数据的4 种简单方法
  • 如何将数据从一部手机传输到另一部手机?
  • SSH 登录失败,封禁IP脚本
  • Oracle 学习笔记
  • 【橘子分布式】Thrift RPC(理论篇)
  • LINUX714 自动挂载/nfs;物理卷
  • 基于STM32的智能抽水灌溉系统设计(蓝牙版)
  • 前端开发中的常见问题及解决方案
  • 数据结构——优先队列(priority_queue)的巧妙运用
  • 渗透第一次总结
  • 【Python办公】Python如何批量提取PDF中的表格