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("❀!");
  }
}

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

总结

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


相关文章
|
2天前
|
安全 Java
深度解读Java的继承和多态的特性
深度解读Java的继承和多态的特性
|
5天前
|
Java 编译器
Java多态(如果想知道Java中有关多多态的知识点,那么只看这一篇就足够了!)
Java多态(如果想知道Java中有关多多态的知识点,那么只看这一篇就足够了!)
|
7天前
|
Java 数据安全/隐私保护
Java基础之类封装、继承、多态
Java基础之类封装、继承、多态
11 4
|
5天前
|
存储 Java C语言
Java面向对象课程设计--类管理系统
Java面向对象课程设计--类管理系统
12 1
|
5天前
|
Java
Java面向对象特征(二)----- 继承
Java面向对象特征(二)----- 继承
Java面向对象特征(二)----- 继承
|
7天前
|
Java 数据安全/隐私保护
Java基础之类封装、继承、多态
Java基础之类封装、继承、多态
10 2
|
8天前
|
Java 数据安全/隐私保护 开发者
Java是一种完全支持面向对象编程的语言,其面向对象特性包括封装、继承、多态和抽象等
【6月更文挑战第18天】**面向对象编程(OOP)通过对象封装状态和行为,实现问题域的抽象。Java全面支持OOP,核心特性包括**: - **封装**:保护数据安全,隐藏内部细节。 - **继承**:子类继承父类属性和行为,促进代码重用。 - **多态**:一个接口多种实现,增强灵活性和扩展性。 - **抽象**:通过接口和抽象类抽离共性,简化复杂性。 **Java的OOP便于理解和解决复杂系统问题。**
20 3
|
7天前
|
安全 Java 数据安全/隐私保护
Java基础之类封装、继承、多态
Java基础的封装、继承和多态是OOP的核心。封装通过访问控制(如private)隐藏类的内部细节,提供公共接口供外部交互。例如,`Person`类封装`name`和`age`,通过`getName()`和`setAge()`方法访问。继承允许子类(如`Dog`)继承父类(如`Animal`)的属性和方法,并可扩展或覆盖。多态使得父类引用可指向子类对象,调用方法时根据实际对象类型执行,如不同动物的`makeSound()`。接口实现多态提供了一种定义行为而不必关心实现的方式。向上转型(子类→父类)安全且默认,而向下转型(父类→子类)需类型检查以避免异常。
8 1
|
8天前
|
安全 Java 开发者
类与对象:Java中的封装、继承与多态
Java面向对象三大特性:封装(隐藏对象细节,增强安全与复用),继承(代码复用与扩展,如Dog继承Animal),多态(统一接口,不同实现,如Playable接口的Piano和Guitar)。通过示例展示了如何在实践中应用这些概念。【6月更文挑战第16天】
16 2
|
1天前
|
Java 机器人 程序员
深入理解Java中的类与对象:封装、继承与多态
深入理解Java中的类与对象:封装、继承与多态