在讲解多态之前,需要一些预备知识
多态需要满足的条件:
1.必须在继承体系之下(已讲)
2.子类必须要对父类当中的方法进行重写
3.向上转型
一.重写
(一)重写的概念
需求:
例如:若干年前的手机,只能打电话,发短信,来电显示只能显示号码,而今天的手机在来电显示的时候,不仅仅可以显示号码,还可以显示头像,地区等。在这个过程当中,我们 不应该在原来老的类上进行修改,因为原来的 类,可能还在有用户使用 ,正确做法是: 新建一个新手机的类,对来电显示这个方法重写就好了,这样就达到了我 们当今的需求了 。
重写
(override)
:也称为覆盖。重写是子类对父类非静态、非
private
修饰,非
final
修饰,非构造方法等的实现过程进行重新编写,
返回值和形参都不能改变
。
即外壳不变,核心重写!
重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
public class Animal { String name; int age; public Animal(String name, int age) { this.name = name; this.age = age; } public void eat(){ //父类中的eat System.out.println(name + "正在吃饭"); } } public class Dog extends Animal { public Dog(String name,int age){ super(name,age); //使用super初始化从父类继承来的属性 } @Override public void eat(){ //子类中的eat System.out.println(name + "正在吃狗粮"); } } public class Cat extends Animal { public Cat(String name,int age){ super(name,age); //使用super初始化从父类继承来的属性 } @Override public void eat(){ //子类中的eat System.out.println(name + "正在吃猫粮"); } }
此时此刻,父类和子类中都有eat()方法,那么根据概念 重写是子类对父类实现过程进行重新编写,我们可以对以上代码得出结论:Dog类中的eat()成员方法重写了父类中Animal()成员方法~~~
【方法重写的规则】1.子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 ( 参数列表 ) 要完全一致2.被重写的方法返回值类型可以不同,但是 必须是具有父子关系的3. 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被 public 修饰,则子类中重写该方法就不能声明为 protected;4.父类被 static 、 private 修饰的方法、构造方法都不能被重写。如果被private修饰,那么意味着只能在一个类中进行操作,这样就没有子类和父类之说如果被static修饰的方法,那就是类方法,与对象无关。而我们重写是为了方便改变对象内的一些属性5. 重写的方法 , 可以使用 @Override 注解来显式指定 . 有了这个注解能帮我们进行一些合法性校验 . 例如不小心将方法名字拼写错了 ( 比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法 , 就会编译报错 , 提示无法构成重写 .
(二)重写和重载的区别
即:方法重载是一个类的多态性表现
,
而方法重写是子类与父类的一种多态性表现
(三)动态绑定和静态绑定(下面会用到)
这里先简单介绍一下概念~~~~
静态绑定
:也称为前期绑定
(
早绑定
)
,即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代
表函数重载。
动态绑定
:也称为后期绑定
(
晚绑定
)
,即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体
调用那个类的方法。
二.向上转型
(一)介绍
向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。
语法格式:父类类型 对象名 = new 子类类型 ()看下面一行代码就理解了:Animal animal = new Cat("小猫咪",2);
Animal是我们的父类,cat是我们的子类,此时就发生了向上转型
也就是可以通过父类的引用,可以创建一个子类对象~~~
animal是父类类型,但可以引用一个子类对象,因为是从小范围向大范围的转换。
看图片可能更看理解:
(二)使用场景
1. 直接赋值
Animal animal = new Cat("小猫咪",2); //此处就是直接赋值!!!
2. 方法传参
public class Animal { String name; int age; public Animal(String name, int age) { this.name = name; this.age = age; } public void eat(){ //父类中的eat System.out.println(name + "正在吃饭"); } } public class Dog extends Animal { public Dog(String name,int age){ super(name,age); //使用super初始化从父类继承来的属性 } @Override public void eat(){ //子类中的eat System.out.println(name + "正在吃狗粮"); } } public class Cat extends Animal { public Cat(String name,int age){ super(name,age); //使用super初始化从父类继承来的属性 } @Override public void eat(){ //子类中的eat System.out.println(name + "正在吃猫粮"); } } 这是测试代码~~~~~~~~~~~~~~~ public class TestAnimal { public static void eat(Animal a) { //向上转型(通过方法传参) a.eat(); } public static void main(String[] args) { Cat cat = new Cat("小白",16); Dog dog = new Dog("小狗狗",18); eat(cat); eat(dog); } }
我们来看这段代码:
我们自己写了个方法,这个方法可以接受Animal父类对象的引用(也可以接受他的子类对象引用)
正常我们new了Cat 和 Dog对象
然后我们通过eat(Animal a)方法将cat引用和dog引用传给了Animal引用,此时父类Animal引用就指向了子类的对象~~~
注意:
// 编译器在编译代码时,并不知道要调用 Dog 还是 Cat 中 eat 的方法// 等程序运行起来后,形参 a 引用的具体对象确定后,才知道调用那个方法( 动态绑定!!! )// 注意:此处的形参类型必须时父类类型才可以
3. 方法返回
public static Animal buyAnimal(String var){ if("狗".equals(var) ){ return new Dog("狗狗",1); }else if("猫" .equals(var)){ return new Cat("猫猫", 1); }else{ return null; } }
三.多态
同过前面的介绍,我们已经知道了重写和向上转型的知识
其实到这个时候,我们已经写出来多态啦~~~
我们可以看下面代码:
我们要按规定打印 :
圆形 矩形 圆形 矩形 花
package NewOOP.poly; class Shape { //属性.... public void draw() { System.out.println("画图形!"); } } class Rect extends Shape{ @Override public void draw() { System.out.println("画矩形"); } } class Cycle extends Shape{ @Override public void draw() { System.out.println("画圆形"); } } class Flower extends Shape { @Override public void draw() { System.out.println("❀"); } } class test { 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(); } } } }
这样写完全没问题,可以打印出来,但是如果通过多态
👇
public static void drawShapes() { // 我们创建了一个 Shape 对象的数组. //相当于向上转型,比如数组中第一个元素子类Cycle对象的类型是父类Shape类型 Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),new Rect(), new Flower()}; for (Shape shape : shapes) { shape.draw(); //调用那个子类,就可以显示出不同的打印(动态绑定) } }
对于类的调用者来说 (drawShapes 方法 ), 只要创建一个新类的实例就可以了 , 改动成本很低 .而对于不用多态的情况 , 就要把 drawShapes 中的 if - else 进行一定的修改 , 改动成本更高