多态
多态是指用同样的对象调用同样的方法,但是做了不同的事情。即同一个接口,使用不同的实例而执行不同操作。
多态是一种思想方法,从语法层次上的体现形式为:
1)向上转型
2)动态绑定
3)方法覆写(override)override
1. 向上转型
表示父类和子类的类型转换。
向上转型常见的三种体现形式:
- 直接赋值
- 作为方法的参数
- 作为方法的返回值
一个子类的对象,可以使用一个父类的引用去表示。
1)直接赋值
Animal bird = new Bird("圆圆");
2)作为方法的参数
public class Test {
public static void main(String[] args) {
Bird bird = new Bird("圆圆");
feed(bird);
}
public static void feed(Animal animal) {
animal.eat("谷子");
}
}
此时形参 animal 的类型是 Animal (基类), 实际上对应到 Bird (父类) 的实例。
3)作为方法的返回值
public class Test {
public static void main(String[] args) {
Animal animal = findMyAnimal();
}
public static Animal findMyAnimal() {
Bird bird = new Bird("圆圆");
return bird;
}
}
此时,方法 findMyAnimal 返回的是一个 Animal 类型的引用,但是实际上对应的是 Bird 的实例。
2. 动态绑定
此处的动态,指的是程序运行时,而不是编译时。
Animal.java
public class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println("我是一只小动物");
System.out.println(name + "正在吃" + food);
}
}
Bird.java
public class Bird extends Animal {
public Bird(String name) {
super(name);
}
public void eat(String food) {
System.out.println("我是一只小鸟");
System.out.println(this.name + "正在吃" + food);
}
}
Test.java
public class Test {
public static void main(String[] args) {
Animal animal1 = new Animal("圆圆");
animal1.eat("谷子");
Animal animal2 = new Bird("圆圆");
animal2.eat("谷子");
}
}
执行结果
animal1 和 animal2 虽然都是 Animal 类型的引用, 但是 animal1 指向 Animal 类型的实例, animal2 指向 Bird 类型的实例.。
针对 animal1 和 animal2 分别调用 eat 方法, 发现 animal1.eat() 实际调用了父类的方法, 而 animal2.eat() 实际调用了子类的方法。
父类和子类可能会包含名字相同,逻辑不同的方法。调用这个方法时,到底是执行父类的方法还是子类的方法,是在程序运行过程中确定的,而不是编译过程中确定的。
3. 方法覆写
子类实现父类的同名方法, 并且参数的类型和个数完全相同, 这种情况称为 覆写/重写/覆盖(Override)。此处指运行时的多态。
方法覆写的规则:
- 1)方法名相同
- 2)参数完全相同(个数和类型都相同)
- 3)返回值相同
- 4)子类的方法访问权限不能低于父类的访问权限
@Override功能有两个方面:
- 1)让代码读者更清楚的理解这个方法是要重写的
- 2)做出一些编译的检查,万一手误写错了参数,这时没有按照预期构成重写,编译就能检查出来
普通方法可以重写, static 修饰的静态方法不能重写。
使用多态
(类的实现者)
public class Shape {
public void draw();
}
public class Circle extends Shape {
@Override
public void draw() {
//绘制一个圆
System.out.println("○");
}
}
public class Rect extends Shape {
@Override
public void draw() {
System.out.println("□");
}
}
public class Flower extends Shape {
@Override
public void draw() {
System.out.println("❀");
}
}
Test.java (类的调用者)
public class Test {
public static void main(String[] args) {
Shape s1 = new Circle();
Shape s2 = new Rect();
Shape s3 = new Flower();
drawShape(s1);
drawShape(s2);
drawShape(s3);
}
public static void drawShape(Shape s) {
s.draw();
}
}
当类的调用者在编写 drawShape 这个方法的时候, 参数类型为 Shape (父类), 此时在该方法内部并不知道, 也不关注当前的 shape 引用指向的是哪个类型(哪个子类)的实例. 此时 shape 这个引用调用 draw 方法可能会有多种不同的表现 (和 shape 对应的实例相关), 这种行为就称为多态。
多态是封装的更近一步:
- 封装是为了让类的调用者不需要知道类的实现细节(还是得知道这个类是什么类)
- 多态这里类的调用者都不需要知道这个类是什么类型,只需要知道这个类有一个draw方法就可以了
多态总结
使用多态好处:
- 类调用者对类的使用成本进一步降低
- 能够降低代码的“圈复杂度”,避免使用大量的“if - else”
- “圈复杂度”指的是一段代码的复杂程度,通常计算为代码中分支 / 循环语句中的个数。
- 如果一 个方法的圈复杂度太高, 就需要考虑重构。
- 如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低。
- 举例:
class Triangle extends Shape {
@Override
public void draw() {
System.out.println("△");
}
}
对于类的调用者来说(drawShape方法), 只要创建一个新类的实例就可以了, 改动成本很低。