多态
向上转型
Animal animal = new Bird();
像这种形式,父类的引用指向一个子类的实,这种称之为向上转型
父类的引用只能访问自己的东西,不能访问子类特有的属性或者方法
一句话概括:父类引用 引用 子类对象
向上转型发生的时机:
共三种:
- 直接赋值
Animal animal = new Bird();
- 方法传参
public static void func(Animal animal) {
}
public static void main(String[] args) {
Dog dog = new Dog();
func(dog);
}
- 方法返回值
public static Animal func() {
Dog dog = new Dog();
return dog;
}
向下转型
向下转型会不安全,最好不要用或者少用
Animal animal = new Dog();
Dog dog = (Dog)animal; //需要强制类型转换
dog.wangwang(); //此时可以调用Dog特有的方法
Animal animal = new Dog();
//这里需要判断一下animal是不是Bird的一个实例,是的话才能向下转型
if(animal instanceof Bird) {
Bird bird = (Bird)animal;
bird.fly(); //此时可以访问Bird的特有方法
}
A instanceof B
A是否是B的实例
A之前是否引用了B这个对象
运行时绑定
class Animal {
public String name;
public int age;
public void eat() {
System.out.println("Animal::eat()");
}
}
class Dog extends Animal1{
}
public class Test {
public static void main(String[] args) {
Animal animal = new Dog();
animal.eat();
}
}
此时运行会打印Animal::eat()
而我们给Dog重写一下eat方法
class Animal {
public String name;
public int age;
public void eat() {
System.out.println("Animal::eat()");
}
}
class Dog extends Animal1{
public void eat() {
System.out.println("Dog::eat()");
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Dog();
animal.eat();
}
}
此时就会打印Dog::eat() 了
这就是因为在编译时期发生了运行时绑定,也称作动态绑定。
发生动态绑定时的条件
- 父类引用 引用子类对象
- 通过父类的引用 调用父类和子类的同名重写方法
此时就会发生动态绑定,这也时多态的前提
重写
这里说到了重写,那就又不得不回顾一下重载了。
重载(overload):[在同一个类当中]
- 方法名相同
- 参数列表不同
- 返回值不做要求
重写(override): [在继承关系中]
也叫做覆盖,覆写
- 方法名相同
- 参数列表相同
- 返回值相同
注意:
- 子类的访问权限 不能低于 父类的访问权限
- 要重写的方法一定不可以是static方法
- 要重写的方法一定不可以被final修饰
理解多态
先举个例子
class Shape {
public void draw() {
}
}
class Rect extends Shape {
@Override
public void draw() {
System.out.println("♦");
}
}
class Cricle extends Shape {
@Override
public void draw() {
System.out.println("⭕");
}
}
class Flower extends Shape {
@Override
public void draw() {
System.out.println("❀");
}
}
//===============================================================
public class TestDemo {
public static void drawMap(Shape shape) {
shape.draw();;
}
public static void main(String[] args) {
Rect rect = new Rect();
drawMap(rect);
Cricle cricle = new Cricle();
drawMap(cricle);
Flower flower = new Flower();
drawMap(flower);
}
}
在这个代码中, 分割线上方的代码是 类的实现者 编写的, 分割线下方的代码是 类的调用者 编写的.
当类的调用者在编写 drawMap 这个方法的时候, 参数类型为 Shape (父类),此时在该方法内部并不知道, 也不关注当前的 shape 引用指向的是哪个类型(哪个子类)的实例. 此时 shape 这个引用调用 draw 方法可能会有多种不同的表现(和 shape 对应的实例相关), 这种行为就称为 多态
多态的好处
- 类调用者对类的使用成本进一步降低
- 封装是让类的调用者不需要知道类的实现细节
- 多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可
- 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else
基于刚才的代码,如果不基于多态,代码就会很长很复杂
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();
}
}
}
如果用多态来实现,不必写这么多的 if - else 分支语句, 代码更简单
public static void drawShapes() {
// 我们创建了一个 Shape 对象的数组.
Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),
new Rect(), new Flower()};
for (Shape shape : shapes) {
shape.draw();
}
}
- 可扩展能力更强
如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低
class Triangle extends Shape {
@Override
public void draw() {
System.out.println("△");
}
}
核心
多态的核心都是让调用者不必关注对象的具体类型