0
点赞
收藏
分享

微信扫一扫

JavaSE基础(九)---多态的“女少”处

江南北 2022-04-19 阅读 75
java

我将把多态分为以下几点进行解剖和理解: 

 

目录

一、多态

1、多态的概念        

2、多态的前提条件

二、方法的重写

1、什么是重写?

2、重写的规则

3、重写和重载的区别

动态绑定 :

静态绑定:

4、避免在构造方法中调用重写的方法

三、向上转型和向下转型

1、向上转型

 2、向上转型的三种方式

2.1、直接赋值

2.2、通过方法的传参

 2.3、方法返回值

3、向下转型

四、多态的优缺点 

1、多态的优点

1.1、降低代码圈复杂度

1.2、可扩展能力更强

2、 多态的缺点

五、总结(多态的注意事项) 


一、多态

1、多态的概念        

        说到多态,按照文字的表面理解,我们通常会认为是”多种不同形态“。

        具体点多态是当不同的对象去执行完成同一件事情时,会产生不同的状态

        比如现实生活中,猫和狗都是动物,它们会有一些共有的特性(饮食习惯,身体构造之类),虽说猫和狗一定会进食,但作为物种的不同,狗会吃狗粮,猫会吃猫粮;狗和猫都会叫,但是狗是”汪汪汪“地叫,而猫是”喵喵喵“地叫。

 

        比如我们平时使用的打印机,打印机其中会有黑白打印机和彩色打印机,同为打印机的它们都能打印东西,但是同样的打印行为,打印出来的纸张效果是不一样。

 总结以上的特点,同一件事情发生在不同的对象身上,会产生不同的行为结果,这就是多态。

要具体使用多态的这种特性,需要在特定条件下才能使用,接下来我将逐一介绍实现的条件和过程。


2、多态的前提条件

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

  1.     必须在继承体系下   
  2.     子类必须要对父类中方法进行重写(父类得引用对应的子类对象)
  3.     通过父类的引用调用重写的方法

多态的实现:

        运行代码时,传递不同的子类对象给父类对象,之后通过调用父类和子类中构成重写的方法,就会执行对应类的方法。期间父类引用子类对象会发生向上转型。 

 注意:

        编译器在编译代码的时候,并不知道父类调用重写方法时,具体调用的是哪个子类对象,在程序运行起来的时候,会先判断其父类的引用对象类型,再根据实际的类型调用其相应的方法。运行过程中即会发生“动态绑定”。(下面将会介绍)


          此处你们可能会发现代码中的父类Animal和子类Dog和Cat内,都存在eat()方法,而且除了内部语法不一样之外,方法名,返回类型,甚至连参数列表都一模一样。这些方法之间构成了方法的重写。

        之前的方法重载是参数列表不一样,返回类型和变量名一样。        

 

二、方法的重写

1、什么是重写?

重写(override)

        重写也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!

        重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。


2、重写的规则

1、子类在重写父类的方法时,一般必须与父类方法原型一致:修饰符 返回值类型 方法名(参数列表) 要完全一致。 

2、其实被重写的方法返回值类型可以不同,但是必须是具有父子关系的。这种类型关系称为:方法重写的协变。(协变类型,如果重写的时候,返回值之间构成继承关系,则称为协变)

3、子类的访问权限等级不能比父类的访问权限更低,例如:如果父类的一个方法被声明public,那么在子类中重写该方法就不能声明为 protected,只能为public了。或者父类一个方法被声明为protected,那么子类的重写方法可以声明为pubilc或protected。(最起码与父类的访问权限等级相等或更高)。

4、父类的成员方法只能被它的子类重写。

5、子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。

6、子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。

7、父类被static、private修饰的方法和构造方法都不能被重写

8、声明为 final 的方法不能被重写。

9、重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心 将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法构成重写.


 

3、重写和重载的区别

区别点重载(override)重写(override)        
参数列表必须修改一定不能修改
返回类型可以修改一定不能修改
访问限定符可以修改一定不能做更严格的限制(可以降低限制)

 方法的重载:一个类的多态性表现

                        方法之间构成的重载,其方法的返回类型不作要求,但变量名需一样,而参数列表一定要有所不同(参数数量、参数类型、参数顺序上的不同)。

 方法的重写:子类与父类的一种多态性表现。

=========================================================================

 我们在编译时调用父类的重写方法,编译器并不会知道父类对象会具体调用引用的哪个子类重写方法,但是在运行后,却能准确地调用相应的子类重写方法。这是为什么呢?        

         这时就引出了一个名词---“动态绑定”

动态绑定 :


静态绑定:


 

4、避免在构造方法中调用重写的方法

下面以一个代码为例:

此代码中:

  • 构造Cat的对象时,会调用Animal的构造方法
  • Animal的构造方法中调用了eat方法, 此时会触发动态绑定, 会调用到 Cat中的eat

 以后在我们写代码的时候,尽量不要出现类似的代码,可能会出现一些隐藏的但是又极难发现的问题。我们要避免在构造方法中调用重写的方法了!


三、向上转型和向下转型

1、向上转型

以animal为例,animal是父类类型,但可以引用一个子类对象,因为是从小范围向大范围的转换。 (注意:继承关系,一次只能继承一个类)

 

我们知道平时new一个对象后,可能调用对应类里的成员方法。

 还需注意的是不能调用其他类中的方法,即使两个类之间构成了继承关系!

 

 2、向上转型的三种方式

2.1、直接赋值

public class TestDemo6 {
        public static void main(String[] args) {
            Animal animal= new Cat();
            animal.eat();
        }
}

2.2、通过方法的传参

public static void eat(Animal animal){
     animal.eat();
}
public static void main(String[] args) {
     eat(new Cat());
}

 2.3、方法返回值

public static Animal eat(){
    return new Cat("小黄");
}
    public static void main(String[] args) {
    eat();
}

 


public class TestAnimal {
    // 2. 方法传参:形参为父类型引用,可以接收任意子类的对象
    public static void eatFood(Animal a){
    a.eat();
}

    // 3. 作返回值:返回任意子类对象
public static Animal buyAnimal(String var){
    if("狗" == var){
        return new Dog("狗狗",1);
    }else if("猫" == var){
        return new Cat("猫猫", 1);
    }else{
            return null;
    }
}

public static void main(String[] args) {
// 1. 直接赋值:子类对象赋值给父类对象
        Animal cat = new Cat("元宝",2); 
        Dog dog = new Dog("小七", 1);
        eatFood(cat);
        eatFood(dog);
        Animal animal = buyAnimal("狗");
        animal.eat();
        animal = buyAnimal("猫");
        animal.eat();
}

3、向下转型

向下转型和向上转型的转向:

         


向下转型其实是很不安全的。万一在使用的时候转换失败,运行时就会抛异常。


 

四、多态的优缺点 

1、多态的优点

1.1、降低代码圈复杂度

 

例如我们现在需要打印的不是一个形状了, 而是多个形状. 如果不基于多态, 实现代码如下:
 

public static void drawShapes() {
    Rect rect = new Rect();
    Cycle cycle = new Cycle();
    Flower flower = new Flower();
    String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"};
    for (String shape : shapes) {
        if (shape.equals("cycle")) {
            cycle.draw();
        } else if (shape.equals("rect")) {
            rect.draw();
        } else if (shape.equals("flower")) {
            flower.draw();
        }
    }
}

 如果使用使用多态, 再通过一个for-each循环则不必写这么多的 if - else 分支语句, 代码更简单.

public static void drawShapes() {
    // 创建了一个 Shape 对象的数组.
    Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),
        new Rect(), new Flower()};
    for (Shape shape : shapes) {
        shape.draw();
    }
}
//Shape为自定义类类型,定义一个自定义类数组,每一个元素都是一个实例化的对象,
//发生了向上转型

1.2、可扩展能力更强

通过多态,我们可以很方便地对其功能的扩展,不用过于麻烦地改动。

以上面打印图形形状为例,如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低.

新增打印三角形

class Triangle extends Shape {
    @Override
    public void draw() {
    System.out.println("△");
    }
}

 


2、 多态的缺点

五、总结(多态的注意事项) 


有关Java中的三大特性介绍,到这里就已经完结了。

如果文章对各位看官有所帮助,希望能点一个免费赞支持支持!

要是有哪个地方写的不好,或哪个地方出现错误,也希望各位大佬能帮忙纠正错误,我会多加改进

让我们一起努力,一起加油吧

举报

相关推荐

0 条评论