Java面向对象之——多态
创始人
2024-04-13 06:54:34
0

文章目录

  • 一、多态的概念
  • 二、多态的条件
  • 三、重写
  • 四、向上转型和向下转型
    • 1、向上转型
    • 2、向下转型
  • 五、再谈多态
  • 六、多态的优缺点
  • 总结

一、多态的概念

多态是同一个行为具有多个不同表现形式或形态的能力。就比如人吃饭,对于中国人使用筷子吃饭,美国人使用刀叉,印度人用手,不同的对象对同一个方法的调用表现出的行为是不一样的。

假如现在有一个Shap的基类,同时有一个drow()方法用来画图形,Shap是一个抽象的类,又可以派生出一些子类如Circle-画圆、Triangle-画三角形、Square-画正方形。如何将Shap-画图这件事情,发生在不同对象身上,产生不同的结果,这就是我们多态要研究的内容。

二、多态的条件

在java中要实现多态,必须要满足如下几个条件,缺一不可:

  1. 必须在继承体系下
  2. 子类必须要对父类中方法进行重写
  3. 向上转型:父类引用指向子类对象Parent p = new Child();
  4. 通过父类的引用调用重写的方法

多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。——动态绑定

上面的4种条件中,继承和方法的调用我们已经很熟悉了,下面就围绕,重写和转型进行详细介绍:

三、重写

重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。

方法重写的规则

  1. 子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致 被重写的方法返回值类型可以不同,但是必须是具有父子关系的。
  2. 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为 protected.
  3. 父类被static、private、final修饰的方法、构造方法都不能被重写。
  4. 重写的方法, 可以使用 @Override 注解来显式指定。有了这个注解能帮我们进行一些合法性校验. 例如不小心将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法构成重写。

重写的设计原则

对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容,并且添加或者改动(重写)新的内容。比如新旧手机:

重写和重载的区别

区别点重写(override)重载(override)
参数列表一定不能修改必须修改
返回类型一定不能修改【除非可以构成父子类关系】可以修改
访问限定符一定不能做更严格的限制(可以降低限制)可以修改

通常认为:方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。

其实重写的返回值类型一般是不做修改的,但是也有可修改的情况,这里我查阅了一下,大家可以看一下这个例子:

    class Test1 { public Object workO() { return new Object(); } } class Test2 extends Test1 {@Override public String workO() { return new String(); } } //其中String是Object的子类型.

☆特别注意☆避免在构造方法中调用重写的方法

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 Test {public static void main(String[] args) {D d = new D();}
}

分析: 我们已知程序执行顺序为:先执行父类静态代码块–>执行子类静态代码块–>执行父类实例代码块–>执行父类构造方法–>执行子类实例代码块–>执行子类构造方法。实例化对象d时会调用子类d的构造方法,在子类构造方法中需要先对基类B的构造方法进行构造,此时会调用方法func()由于func()为重写方法,此时发生动态绑定又回去调用子类D中重写的func()方法,但是此时的num还未赋值默认值为0,所以输出结果为:D.func() 0

结论: 在实际开发中"用尽量简单的方式使对象进入可工作状态", 尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题。

四、向上转型和向下转型

1、向上转型

向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。

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

向上转型的几种应用场景:

1. 直接赋值

shap是父类类型,但可以引用一个子类对象,此时是从子类向父类转换,从小范围向大范围的转换。

Shap shap=new Circle();

2. 父类数组存放子类对象

父类数组可以放子类类型的对象-自动发生向上转型

Shape[] shapes = {cycle,rect,cycle,rect,flower};

3. 方法传参

形参为父类型引用,可以接收任意子类的对象

public static void drowshap(Shap a){a.drow();
}

4. 方法返回

父类接收返回任意子类对象

public static Shap shap() {return new Circle;
}

注意:向上转型后不能调用子类特有的方法。

2、向下转型

向下转型:将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换。

//向上转型
Shap shap = new Circle();
//向下转型,本来指向的就是圆,因此将shap还原为圆也是安全的
Circle cir = (Circle)shap;//非父子关系,需要强制类型转换
//此时可以访问子类的方法
cir.drow();
//向上转型
Shap shap = new Circle();
//向下转型
//shap实际指向的是圆,现在要强制还原为三角,
//无法正常还原,运行时抛出:ClassCastException
Triangle tri = (Triangle)shap;//error

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

instanceof关键字:的作用是判断左边对象是否是右边类的实例,返回的boolean类型,truefalse.

所以我们可以在代码中加入这些代码保障向下转型的安全:

if(shap instanceof Circle){Circle cir = (Circle)shap;cir.drow();
}
if(shap instanceof Triangle){Triangle tri = (Triangle)shap;tri.drow();
}

五、再谈多态

前面我们一直在谈论多态,对于多态,是同一个行为具有多个不同表现形式或形态的能力,而这种能力实现的本质其实是动态绑定,谈到这我们有必要谈谈起动态绑定和静态绑定:

动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。

静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表方法重载

一顿操作下来,实现多态的条件我们以经介绍完毕,对于多态的本质也有了一定的了解。下面我们以本文开头的Shap类简单的实现一下多态:

class Shape {public void draw() {System.out.println("画图形!");}
}
class Square extends Shape {@Overridepublic void draw() {System.out.println("□!");}
}
class Circle extends Shape {@Overridepublic void draw() {System.out.println("○!");}
}
class Triangle extends Shape {@Overridepublic void draw() {System.out.println("△!");}
}public static void drawMap() {Square rect = new Square();Circle cycle = new Circle();Flower triangle = new Triangle();//父类数组可以放子类类型的对象-自动发生向上转型Shape[] shapes = {circle,square,triangle};//调用重写方法for(Shape shape : shapes) {shape.draw();}}public static void main(String[] args) {drawMap();}

六、多态的优缺点

介绍完多态后,我们简单做个总结,盘点一下多态的优缺点。

多态优点:

1.能够降低代码的 “圈复杂度”, 避免使用大量的 if - else(圆圈复杂度:一种代码复杂度的衡量标准)

例如将如上多态写出循环,其中就会出现大量的if-else

    public static void drawMap2() {Square square = new Square();Circle circle = new Circle();Triangle triangle = new Triangle();String[] shapes = {"circle", "square", "triangle"};for (String s : shapes) {if(s.equals("circle")) {circle.draw();}else if(s.equals("square")) {square.draw();}else {triangle.draw();}}}

2.可扩展能力更强
如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低。对于类的调用者来说, 只要创建一个新类的实例就可以了, 改动成本很低。而对于不用多态的情况, 就要把 drawShapes 中的 if - else 进行一定的修改, 改动成本更高。

class Flower extends Shape {@Overridepublic void draw() {System.out.println("❀!");}
}

多态缺点代码运行效率降低

总结

本章主要探讨多态的使用,重点介绍了转型和重写的概念,至此面向对象的三大特性就全部讲完了,这部分内容比较抽象,希望大家多总结,多思考,我们一起快乐编程!🥰

相关内容

热门资讯

银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...