多态
多态首先是建立在继承的基础上的,先有继承才能有多态。多态是指不同的子类在继承父类后分别都重写覆盖了父类的方法,即父类同一个方法,在继承的子类中表现出不同的形式。多态成立的另一个条件是在创建子类时候必须使用父类new子类的方式。
同一操作作用于不同的对象,可以产生不同的效果。这就是多态。
class Shape {
public void draw() {
// 啥都不用干
}
}
class Cycle extends Shape {
@Override
public void draw() {
System.out.println("○");
}
}
class Rect extends Shape {
@Override
public void draw() {
System.out.println("□");
}
}
class Flower extends Shape {
@Override
public void draw() {
System.out.println("♣");
}
}
/我是分割线//
// Test.java
public class Test {
public static void main(String[] args) {
Shape shape1 = new Flower();
Shape shape2 = new Cycle();
Shape shape3 = new Rect();
drawMap(shape1);
drawMap(shape2);
drawMap(shape3);
}
// 打印单个图形
public static void drawShape(Shape shape) {
shape.draw();
}
}
向上转型
上面代码中的
Shape shape1 = new Flower();
Shape shape2 = new Cycle();
Shape shape3 = new Rect();
就是一个向上转型的例子
向上转型:父类引用指向子类的实例
向上转型发生的时机:
直接赋值
方法传参
方法返回
直接赋值
Shape shape1 = new Flower();
这个就是直接赋值
方法传参
public class Test {
public static void main(String[] args) {
Cycle cycle= new Cycle();
drawMap(cycle);
}
public static void drawMap(Shape shape) {
shape.draw();
}
}
方法返回
public class Test {
public static void main(String[] args) {
Shape shape= drawMyShape();
}
public static Shape drawMyShape() {
Cycle cycle= new Cycle();
return cycle;
}
}
动态绑定
调用某个类的方法, 究竟执行了哪段代码 (是父类方法的代码还是子类方法的代码) , 要看究竟这个引用指向的是父类对象还是子类对象. 这个过程是程序运行时决定的(而不是编译期), 因此称为动态绑定。
上面所示例的代码在调用方法时调用的是子类的方法。
重写
子类实现父类的同名方法, 并且参数的类型和个数完全相同, 这种情况称为 覆写/重写/覆盖(Override).
关于重写的注意事项
- 重写和重载完全不一样. 不要混淆
- 普通方法可以重写, static 修饰的静态方法不能重写.
- 重写中子类的方法的访问权限不能低于父类的方法访问权限.
- 重写的方法返回值类型不一定和父类的方法相同(但是建议最好写成相同, 特殊情况除外).
- 针对重写的方法, 可以使用 @Override 注解来显式指定.有了这个注解能帮我们进行一些合法性校验. 例如不小心将方法名字拼写错了 (比如写成 drow), 那么此时编译器就会发
现父类中没有 drow 方法, 就会编译报错, 提示无法构成重写
重写和重载的区别
向下转型
// 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(this.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);
}
public void fly() {
System.out.println(this.name + "正在飞");
}
}
首先发生向上转型,将父类引用指向子类对象,在判断向下转型时这个子类对象和要转型的对象是否是一个实例,若是,则将父类引用强制转换为这个子类对象。
Animal animal = new Cat("小猫");
if (animal instanceof Bird) {
Bird bird = (Bird)animal;
bird.fly();
}
先判定一下看看 animal 本质上是不是一个 Bird 实例, 再来转换
instanceof 可以判定一个引用是否是某个类的实例. 如果是, 则返回 true. 这时再进行向下转型就比较安全
多态的优点
- 类调用者对类的使用成本进一步降低.
封装是让类的调用者不需要知道类的实现细节.
多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可.
因此, 多态可以理解成是封装的更进一步, 让类调用者对类的使用成本进一步降低.
这也贴合了 <<代码大全>> 中关于 “管理代码复杂程度” 的初衷. - 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else
- 可扩展能力更强.
如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低
总结
对于Shape这个例子来说:发生继承,子类重写父类的方法,当发生向上转型时,调用的是子类对象重写父类对象的方法(此时发生了动态绑定)。
对于Animal这个例子来说:发生继承,当发生向下转型时,调用的是子类对象中的方法,不管父类中有没有这个方法。
如果抛开 Java, 多态其实是一个更广泛的概念, 和 “继承” 这样的语法并没有必然的联系.
C++ 中的 “动态多态” 和 Java 的多态类似. 但是 C++ 还有一种 “静态多态”(模板), 就和继承体系没有关系了.
Python 中的多态体现的是 “鸭子类型”, 也和继承体系没有关系.
Go 语言中没有 “继承” 这样的概念, 同样也能表示多态.
无论是哪种编程语言, 多态的核心都是让调用者不必关注对象的具体类型. 这是降低用户使用成本的一种重要方式.