> 文档中心 > 【JavaSE系列】 第九话 —— 多态那些事儿

【JavaSE系列】 第九话 —— 多态那些事儿

目录

 🍚前言

  🧀思维导图

  🍔一、多态的概念

  🧇二、向上转型和向下转型

               🍗🍗2.1 向上转型

               🍞​​​​​​​🍞2.2 什么是向上转型

               🍟​​​​​​​🍟2.3 三种常见的向上转型

                               🥞​​​​​​​🥞​​​​​​​🥞2.3.1 直接赋值

                               🥡​​​​​​​🥡​​​​​​​🥡2.3.2 作为方法的参数

                               🍱​​​​​​​🍱​​​​​​​🍱2.3.3 作为方法的返回值

               🍕​​​​​​​🍕2.4 向下转型(这个了解即可)

  🍝三、方法重写

               🍰​​​​​​​🍰3.1 方法重写的概念

               🍯​​​​​​​🍯3.2 方法重写的规定

               🥣​​​​​​​🥣3.3 在IDEA中使用重写的快捷方式

               🍛​​​​​​​🍛3.4 方法重写中所要注意的细节

  🥩四、多态

               🍣​​​​​​​🍣4.1 什么是多态

               🍤​​​​​​​🍤4.2 多态产生的前提

  🍜五、理解多态

  🥃六、多态的优缺点

               🥮​​​​​​​🥮6.1 使用多态的好处

               🍲​​​​​​​🍲6.2 多态的缺点

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

 🍠写在最后


前言

       本篇博客 博主来介绍 Java的三大特性的最后一个 特性 —— 多态;

       当然,也包括多态所涉及的多方面内容;

       现在,拉长耳朵,提高警觉,端起你们的小板凳;

       且听我细细道来......


思维导图

 


一、多态的概念

       多态,

       从语文的层次上来说:一种事务,多种形态;这句话不算对,但也不算错;

       但是,我们需要从程序的角度上来介绍:去完成某个行为,当不同的对象去完成时,会产生不同的状态,这就是多态。

       比如说,如下图所示:同样是一个打印机,去打印相同的照片;但是,交给彩色打印机打印出来的就是彩色的照片;交给黑白打印机打印出来的就是黑白照片;它们完成的动作都是“打印”;这就是一种多态;

       再比如说,如下图所示,去“吃饭”,对于小猫来说,吃的是“猫粮”;但对于小狗来说,吃的确是“狗粮”;他们完成的都是“吃饭”这个行为,但是却“吃出不同的结果来”; 这也是一种多态;

总的来说,同一件事情,发生在不同的对象上,会产生不同的结果。 

听到这里,是不是还是对于 多态 有点头晕;

那么,要想真正了解多态,我们还是需要从三个方面来介绍:

  1. 什么是向上转型;
  2. 什么叫做方法重写;
  3. 了解了前两个,我们才会真正了解什么是多态。

下面,我会一一的介绍......


二、向上转型和向下转型

2.1 向上转型

       当然的是,有了 向上转型,那么肯定也会有向下转型,

向下转型 会在后面讲到......

2.2 什么是向上转型

       首先介绍一段平平常常的继承代码:

package Demo1;class Animal {    public String name;    public int age;    public void eat() { System.out.println(this.name+"吃饭!");    }}class Cat extends Animal {    public String hair;    public void mew() { System.out.println(this.name+"正在叫!");    }}public class TestDemo1 {    public static void main(String[] args) { Cat cat = new Cat(); cat.mew();    }}

       那么,如果现在抛开 继承 不谈,直接用Animal类 new一个animal对象,可以发现,animal对象访问不了Animal类 里面没有的成员变量 或成员方法:

       接下来,可以来讲一讲 向上转型 的知识了:

       这里的 上 指的是 父类,那么 下 指的就是 子类

        那么,把子类给父类是什么意思呢?

 //即:定义类一个 cat //可以用animal来接收 //也就是说,父类的引用 可以用来引用 子类的对象 Cat cat = new Cat(); Animal animal = cat; //也就是说,上面的两行代码,可以合并成下面一行代码  Animal animal = new Cat();    //此时,父类的引用 引用了 子类的对象,我们把这个就叫做 向上转型

       但是,此时又有一个新的问题;

       animal的类型是Animal,那么 此时它只能去访问 类Animal 的成员变量和方法,去访问 子类Cat的成员变量或方法的时候会报错:

 

【总结】

       向上转型,把原来的 子类的类型 转换成了 父类的类型,那么,就只能去访问 父类特有的成员方法或者成员变量。

2.3 三种常见的向上转型

2.3.1 直接赋值

       所谓直接赋值,就是 上面的直接把 子类对象 给 父类 进行引用:

 /* Cat cat = new Cat(); Animal animal = cat; */ Animal animal = new Cat();

2.3.2 作为方法的参数

2.3.3 作为方法的返回值

2.4 向下转型(这个了解即可)

前面已经介绍过 向上转型,那么 现在来介绍一下 向下转型:

不过,现在来执行一下这样的操作:

此时,运行结果:

但是,向下转型不安全(不介意使用向下转型):

 我们还需要做以下修改 以保证其安全性:

【注意】

 


三、方法重写

       由上面可知,父类引用引用了子类的对象;

       但是,在现实生活中,猫是吃猫粮的;

       那么,如果想改的话,肯定不可以在父类上面进行修改的;

       毕竟,可能还有 其他的子类 来继承父类;

       那么,如果想修改的话,就需要在子类里面重新取实现一遍这个方法的:

       然后,我们来对比一下 实现前后的结果:

没有在子类里面写eat方法:

在子类里面写了eat方法:

 这是怎么回事呢?

——这就是马上所要介绍的方法重写

3.1 方法重写的概念

       重写,也称为覆盖;

       重写,是子类对父类 非静态、非private修饰、非final修饰、非构造方法 等的实现过程进行重新编写;

       重写的好处是:子类可以根据需要,定义特定的属于自己的行为;如 上面的猫可以吃猫粮,狗可以吃狗粮。

3.2 方法重写的规定

方法重写满足以下三个条件:

  1. 方法的名称相同;
  2. 方法的返回值相同;
  3. 方法的参数列表相同。

       当在子类 方法重写以后,那么就会调用的是 子类重写的内容。

       我们把这个现象叫做动态绑定(这是多态的基础)

       传送门:动态绑定

       在上面所示例中,

       在编译的时候,调用的还是 父类Animal的eat方法;

       但是,在运行的时候,变成了子类Cat自己的eat方法;

       因此,动态绑定又称为 运行时绑定

       即:在程序运行的时候才知道要调用谁。

       当然,有了 动态绑定,那肯定也有 静态绑定:

       在编译期间就已经知道了 要调用的是谁,

       比如说 重载。

3.3 在IDEA中使用重写的快捷方式

       当然,在使用IDEA编译器的时候 ,

       重写不仅仅可以直接在子类上手敲出来的(上面的就是),而且还可以使用快捷键的方式:

 

 

 

【注意】上面的 @Override 就是重写的意思。

 

3.4 方法重写中所要注意的细节

  1. 静态方法(static修饰)是构成不了重写的:
  2. private修饰 的方法不能进行重写:
  3. 如果要进行重写的话,那么 子类的 访问限定修饰符的权限 一定 大于等于 父类的访问限定修饰符
  4. 被final修饰的方法不可以进行重写:
  5. 子类和父类在同一个包中,那么子类可以重写父类的所有方法(除了 声明为private和final的方法);子类和父类不在同一个包,那么子类只能够 重写父类的 声明为public和protected的非final的方法(即 默认权限方法/包访问权限 不可以被重写);
  6. 重写的方法,可以使用 @Override 注解来显示指定;有了这个注解 可以帮助我们进行一些合法性校验;如 不小心把方法名字写错了(写成ate),那么此时编译器就会发现父类中没有ate方法,就会编译报错,提示无法构成重写。

 访问限定符权限大小比较:

private < default < peotected < public  

 


 四、多态

4.1 什么是多态

类的实现者所写的代码:

class Animal {    public String name;    public int age;    public void eat() { System.out.println(this.name+"吃饭!父类Animal");    }}class Cat extends Animal {    public String hair;    public void eat(){ System.out.println(this.name+"吃猫粮!");    }    public void mew() { System.out.println(this.name+"正在叫!");    }}class Dog extends Animal { public void eat(){     System.out.println(this.name+"吃狗粮!");    }}

类的调用者所写的代码:

    public static void function(Animal animal) { animal.eat();    }    public static void main(String[] args) { Cat cat = new Cat(); Dog dog = new Dog(); function(cat); function(dog);    }

 

所以运行之后得到的结果不一样:

 

       从上面可以得到,同一个方法,当引用的对象不一样的时候,这个方法所表现出来的行为是不一样的;

       我们把这种思想就叫做 多态。 

4.2 多态产生的前提

  1. 发生向上转型:父类引用 引用子类的对象;
  2. 发生重写:父类和子类当中 有同名的覆盖方法;
  3. 通过父类引用,调用这个重写的方法,此时会发生 动态绑定。

思想:通过一个引用调用一个方法,由于引用的对象不同,所执行的行为也是不一样的;我们把这种思想就叫做多态的思想。


五、理解多态

现在再次回顾一下多态:

场景:现在想画一个图形(图形是未知的):

首先,创建父类:

class Shape {    //省略了长、宽、高等之类的属性    public void draw(){ System.out.println("画图形!!!!!!");    }}

创建父类Shape只是画父类,但是并没有说明画什么;

现在想画各种各样的图形,那么就可以去重写 Shape类里面的draw方法来满足自己的需求:

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

前面两段代码都是类的实现者写的;

当有一天作为用户、作为类的调用者 想要画出这些图形,那么就可以这样来做:

    public static void drawMap(Shape shape) { shape.draw();    }    public static void main(String[] args) { Cycle cycle = new Cycle(); Rect rect = new Rect(); Triangle triangle = new Triangle();  drawMap(cycle); drawMap(rect); drawMap(triangle);    }

那么,根据引用的对象不一样,draw方法所表现的行为就不一样;

这个就叫做 多态。

代码示例结果:

 

由此可见,多态只是一种思想,需要满足三个条件;

那么,多态到底有什么好处呢?

——其拓展能力非常强:

如果突然说,现在想要画一朵花:

那么只需要在这样做即可:

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

测试类添上这个来测试:

drawMap(new Flower());

代码示例结果:


六、多态的优缺点

6.1 使用多态的好处

(1)能够降低代码的 "圈复杂度" ,避免使用大量的 if-else语句;

            传送门——圈复杂度​​​​​​

           说白了,可以简单粗暴的计算 一段代码中条件语句和循环语句 出现的个数,这个个数就称为 "圈复杂度";

            如果一个方法的 圈复杂度 太高,就需要考虑重构。

(2)可扩展能力强; 

            就是上面所说的 添加了一朵花的 示例。

6.2 多态的缺点

                     代码的运行效率低。


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

class B {    public B(){ func();    }    public void func() { System.out.println("B.func() ");    }}class D extends B {    D(){ super();    }    @Override    public void func() { System.out.println("D.func() ");    }}public class Test {    public static void main(String[] args) { D d = new D();    }}

代码示例结果:

 【说明】

  1. 构造D对象的同时,会调用B的构造方法;
  2. B的构造方法中调用了 func方法,此时会触发 动态绑定,会调用到D中的 func;

【注意】最好尽量不要写类似的代码——避免在构造方法中调用重写的方法。


写在最后

       到现在,Java三大特性的最后一个特性——多态思想 就已经告一段落了;

       由于博主水平有限,可能会出现一些表达不清楚,或者出现一些其他的情况,

       欢迎各位铁汁们指出来,和博主一起改正,

       一起努力,共同进步;

       好了,如果这篇博客对铁汁们有帮助的话,可以送一个免费的 赞 嘛;

       当然,顺手点个关注是再好不过的了......