Java面向对象之——多态

简介: Java面向对象之——多态

一、多态的概念

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

假如现在有一个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;
    @Override
    public 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类型,true和false.

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

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 {
    @Override
    public void draw() {
        System.out.println("□!");
    }
}
class Circle extends Shape {
    @Override
    public void draw() {
        System.out.println("○!");
    }
}
class Triangle extends Shape {
    @Override
    public 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 {
  @Override
  public void draw() {
    System.out.println("❀!");
  }
}

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

总结

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


相关文章
|
3月前
|
算法 Java 程序员
在Java的编程世界里,多态不仅仅是一种代码层面的技术,它是思想的碰撞,是程序员对现实世界复杂性的抽象映射,是对软件设计哲学的深刻领悟。
在Java的编程世界里,多态不仅仅是一种代码层面的技术,它是思想的碰撞,是程序员对现实世界复杂性的抽象映射,是对软件设计哲学的深刻领悟。
64 9
|
8天前
|
Java 关系型数据库 数据库
面向对象设计原则在Java中的实现与案例分析
【10月更文挑战第25天】本文通过Java语言的具体实现和案例分析,详细介绍了面向对象设计的五大核心原则:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。这些原则帮助开发者构建更加灵活、可维护和可扩展的系统,不仅适用于Java,也适用于其他面向对象编程语言。
9 2
|
17天前
|
存储 Java 测试技术
Java零基础-多态详解
【10月更文挑战第10天】Java零基础教学篇,手把手实践教学!
15 4
|
22天前
|
Java 编译器 程序员
Java多态背后的秘密:动态绑定如何工作?
本文介绍了Java中多态的实现原理,通过动态绑定和虚拟方法表,使得父类引用可以调用子类的方法,增强了代码的灵活性和可维护性。文中通过具体示例详细解析了多态的工作机制。
29 4
|
2月前
|
Java 编译器
封装,继承,多态【Java面向对象知识回顾①】
本文回顾了Java面向对象编程的三大特性:封装、继承和多态。封装通过将数据和方法结合在类中并隐藏实现细节来保护对象状态,继承允许新类扩展现有类的功能,而多态则允许对象在不同情况下表现出不同的行为,这些特性共同提高了代码的复用性、扩展性和灵活性。
封装,继承,多态【Java面向对象知识回顾①】
|
29天前
|
Java
java继承和多态详解
java继承和多态详解
38 5
|
27天前
|
存储 Java 测试技术
Java零基础-多态详解
【10月更文挑战第1天】Java零基础教学篇,手把手实践教学!
24 1
|
29天前
|
安全 Java 编译器
【一步一步了解Java系列】:重磅多态
【一步一步了解Java系列】:重磅多态
18 3
|
2月前
|
Java 编译器
Java——类与对象(继承和多态)
本文介绍了面向对象编程中的继承概念,包括如何避免重复代码、构造方法的调用规则、成员变量的访问以及权限修饰符的使用。文中详细解释了继承与组合的区别,并探讨了多态的概念,包括向上转型、向下转型和方法的重写。此外,还讨论了静态绑定和动态绑定的区别,以及多态带来的优势和弊端。
56 9
Java——类与对象(继承和多态)
|
2月前
|
Java
java中面向过程和面向对象区别?
java中面向过程和面向对象区别?
32 4