文章目录

  • 1. 多态
    • 1.1 多态的概念
    • 1.2 多态的实现条件
    • 1.3 重写
    • 1.4 向上转型和向下转型
      • 1.4.1 向上转型
      • 1.4.2 向下转型
    • 1.5 多态的优缺点
    • 1.6 避免在构造方法中调用重写的方法

1. 多态

1.1 多态的概念

多态就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同 的状态。

例如不同的动物吃的食物不一样。

不同的交通工具速度不一样

1.2 多态的实现条件

要实现多态,必须要满足如下几个条件:

  • 必须在继承体系下
  • 子类必须要对父类中方法进行重写
  • 通过父类的引用调用重写的方法

多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。

class Animals {public String name;public int age;public Animals(String name, int age ) {this.name = name;this.age = age;}public void eat() {System.out.println(name + "吃饭");}}class Cat extends Animals{public Cat(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println(name + "吃饭");}}class Dog extends Animals{public Dog(String name, int age) {super(name, age);}@Overridepublic void eat() {//与父类的eat方法构成重写System.out.println(name + "吃饭");}}public class Test {public static void eat(Animals animals) {animals.eat();}public static void main(String[] args) {Dog dog = new Dog("大黄", 6);Cat cat = new Cat("小黑", 6);eat(dog);eat(cat);}}

在编写 eat 这个方法的时候, 参数类型为 Animal (父类), 此时在该方法内部并不知道, 也不关注当前的 animals 引用指向的是哪个类型(哪个子类)的实例。此时 animals 这个引用调用 eat方法可能会有多种不同的表现(和 animals 引用的实例相关), 这种行为就称为多态。

1.3 重写

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

【方法重写的规则】

  • 子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型方法名 (参数列表) 要完全一致。
  • 被重写的方法返回值类型可以不同,但是必须是具有父子关系的。
  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为 protected。
  • 父类被static、private修饰的方法、构造方法都不能被重写。
  • 重写的方法, 可以使用 @Override 注解来显式指定.,有了这个注解能帮我们进行一些合法性校验.。例如不小心将方法名字拼写错了 (比如写成 aet),那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法构成重写。

【重写与重载的区别】

重写方法名和参数都一样。

class Animals {public String name;public int age;public void eat() {System.out.println(name + "吃饭");}public void sleep() {System.out.println(name + "睡觉");}public void play() {System.out.println(name + "做游戏");}}class Cat extends Animals{public Boolean LoveClean;//爱干净public void catchMouse() {System.out.println(name + "抓老鼠");}public void eat() {System.out.println(name + "吃饭");//与父类的eat方法构成重写}}

重载方法名相同,参数不同。

class Cat{public String name = "小黑";public void catchMouse() {System.out.println("抓老鼠");}public void catchMouse(String name) {//构成重载}}

静态绑定: 也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。

动态绑定: 也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。

1.4 向上转型和向下转型

1.4.1 向上转型

向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。

语法格式:父类类型 对象名 = new 子类类型()

Aniamls animals = new Dog("大黄", 7);

向上转型的使用场景:

  • 直接赋值
  • 方法传参
  • 方法返回
class Animal {public String name;public int age;public Animal(String name, int age ) {this.name = name;this.age = age;}public void eat() {System.out.println(name + "吃食物");}}class Cat extends Animals {public Cat(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println(name + "吃食物");}}class Dog extends Animals { public Dog(String name, int age) {super(name, age);}@Overridepublic void eat() {//与父类的eat方法构成重写System.out.println(name + "吃食物");}}public class TestAniamls {// 方法传参:形参为父类型引用,可以接收任意子类的对象public static void eat(Animals animals) {animals.eat();}//作返回值:返回任意子类对象public static Animals byAnimals(String var) {if ("小狗狗".equals(var)) {return new Dog("小狗狗", 1);} else if ("小猫猫".equals(var)) {return new Cat("小猫猫", 1);}else {return null;}}public static void main(String[] args) {//直接赋值:子类对象赋值给父类对象Animals cat = new Cat("小黑", 7);Dog dog = new Dog("大黄", 6);eat(cat);eat(dog);}}

向上转型的优点:让代码实现更简单灵活。

向上转型的缺陷:不能调用到子类特有的方法。

1.4.2 向下转型

将父类引用再还原为子类对象,即向下转型。

向下转型不安全。


因为猫和狗是动物,向上转型安全;但是动物不一定是猫或者狗,所以不安全。

class Animal {public String name;public int age;public Animal(String name, int age ) {this.name = name;this.age = age;}public void eat() {System.out.println(name + "吃食物");}}class Cat extends Animal {public Cat(String name, int age) {super(name, age);}public void catchMouse() {System.out.println(name + "抓老鼠");}@Overridepublic void eat() {System.out.println(name + "吃食物");}}class Dog extends Animal {public Dog(String name, int age) {super(name, age);}public void brak() {System.out.println(name + "汪汪叫");}@Overridepublic void eat() {//与父类的eat方法构成重写System.out.println(name + "吃食物");}}public class TestAnimals {public static void main(String[] args) {Animal animal = new Cat("小黑", 7);Cat cat = (Cat)animal;//向下转型cat.catchMouse();}}

Dog类当中没有抓老鼠这一方法,引用的话会报错。

//部分代码public class TestAnimals {public static void main(String[] args) {Animal animal = new Dog("大黄", 7);Cat cat = (Cat)animal;//向下转型cat.catchMouse();}}

如果想要避免运行后报错,可以使用 instanceof关键字。

public class TestAnimals {public static void main(String[] args) {Animal animal = new Dog("大黄", 7);if (animal instanceof Cat) {Cat cat = (Cat)animal;//向下转型cat.catchMouse();}}}

如果反生错误的话会返回一个 flase。

1.5 多态的优缺点

先来看一段代码:

一个用来画三种图形的代码

package demo1;class Graph {public void draw() {System.out.println("画图形");}}class Rectangle extends Graph{@Overridepublic void draw() {System.out.println("画矩形!");}}class Cycle extends Graph{@Overridepublic void draw() {System.out.println("画圆!");}}class Triangle extends Graph{@Overridepublic void draw() {System.out.println("画三角形!");}}public class Test {public static void main(String[] args) {Graph graph1 = newRectangle();graph1.draw();Graph graph2 = new Cycle();graph2.draw();Graph graph3 = new Triangle();graph3.draw();}}

利用多态来进行优化:

package demo1;class Graph {public void draw() {System.out.println("画图形");}}class Rectangle extends Graph{@Overridepublic void draw() {System.out.println("画矩形!");}}class Cycle extends Graph{@Overridepublic void draw() {System.out.println("画圆!");}}class Triangle extends Graph{@Overridepublic void draw() {System.out.println("画三角形!");}}public class Test {public static void drawMap(Graph graph) {graph.draw();}public static void main(String[] args) {drawMap(new Rectangle());drawMap(new Cycle());drawMap(new Triangle());}}

同样的drawMap方法,在做着不同的事情,说明这是一个多态。

多态使代码变得简洁了。

【使用多态的好处】

  1. 能够降低代码的 “圈复杂度”, 避免使用大量的 if – else。
  2. 如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低。

在上面的代码中如果先要实现新的类直接添加就可以了。

//部分代码class Apple extends Graph{public void draw() {System.out.println("画苹果!");}}public class Test {public static void drawMap(Graph graph) {graph.draw();}public static void main(String[] args) {drawMap(new Rectangle());drawMap(new Cycle());drawMap(new Triangle());drawMap(new Apple());}}

这里可以说明使用多态后,改动也比较方便。

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

先来看一段代码

package demo1;class B {public B() {// do nothingfunc();}public void func() {System.out.println("B.func()");}}class D extends B {private int num = 1;@Overridepublic void func() {System.out.println("D.func() " + num);}}public class TestDemo {public static void main(String[] args) {D d = new D();}}

这段代码也该是会输出 D.func() 1。

但是结果却是:

说明一开始num就没有被初始化为1。

  • 构造 D 对象的同时, 会调用 B 的构造方法。
  • B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的func。
  • 此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0. 如果具备多态性,num的值应该是1。
  • 所以在构造函数内,尽量避免使用实例方法,除了final和private方法。

结论:

“用尽量简单的方式使对象进入可工作状态”, 尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题