多态(Polymorphism)
多态是同一个行为具有多个不同表现形式或者形态的能力,是对象多种表现形式的表现。
比如我们说"宠物"这个对象,它就有很多不同的表达或实现,比如有小猫、小狗、蜥蜴等等。那么我到宠物店说"请给我一只宠物",服务员给我小猫、小狗或者蜥蜴都可以,我们就说"宠物"这个对象就具备多态性。
又比如我们按下F1键这个动作:
- 如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;
- 如果当前在 Word 下弹出的就是 Word 帮助;
- 在 Windows 下弹出的就是 Windows 帮助和支持。
同一个时间发生在不同的对象上会产生不同的结果。
实例:
public interface Vegetarian{}
public class Animal{}
public class Deer extends Animal implements Vegetarian{}
上述例子证明了Deer具有多重继承,所以其具有多态性,以上可解析如下:
字面意思:鹿对象继承了动物类且实现了素食类动物的接口。
- 一个 Deer IS-A(是一个) Animal
- 一个 Deer IS-A(是一个) Vegetarian
- 一个 Deer IS-A(是一个) Deer
- 一个 Deer IS-A(是一个)Object
在Java中,所有的对象都具有多态性,因为任何对象都能通过IS-A的测试的类型和object类。
访问一个对象的唯一方法就是通过引用型变量,引用型变量只有一种类型,一旦被声明,引用型变量的类型就不能再改变了。引用型变量不仅能被重置为其它变量,前提是这些变量没有被声明为final,还可以引用和它类型相同的或者相互兼容的对象,它可以声明为类类型或者接口类型。
多态的优点:
- 消除类型之间的耦合性;
- 可替换性;
- 可扩充性;
- 接口性;
- 灵活性;
- 简化性;
多态存在的三个必要条件:
继承、重写和父类引用指向子类的对象。
PolymorphismDemo.java
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
@Override
void eat() {
System.out.println("吃鱼");
}
public void work() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
@Override
void eat() {
System.out.println("吃骨头");
}
public void work() {
System.out.println("看家");
}
}
public class PolymorphismDemo {
public static void main(String[] args) {
show(new Cat()); //Cat对象调用show方法
show(new Dog()); //Dog对象调用show方法
Animal animal = new Cat();//向上转型
animal.eat(); //调用的是Cat的eat()
Cat cat = (Cat) animal; //向下转型
cat.work(); //调用Cat的work()
}
public static void show(Animal animal) {
animal.eat();
if (animal instanceof Cat) { //判断类型
Cat cat = (Cat) animal;
cat.work();
} else if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.work();
}
}
}
结果:
吃鱼
抓老鼠
吃骨头
看家
吃鱼
抓老鼠Process finished with exit code 0
关于多态
面向对象的三大特性:封装、继承、多态。从一定角度来看,封装和继承几乎都是为多态而准备的。这是我们最后一个概念,也是最重要的知识点。
定义:指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)
实现多态技术:动态绑定(dynamic binding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。
面试题
1. 多态分为几种,有什么特点?
参考答案:
- 编译时多态(设计时多态):方法重载;
- 运行时多态:Java运行时系统根据调用该方法的实例的类型来决定选择调用哪个方法则被称为运行时多态。
2. 输出下列结果:
Test.java
class A {
public String run(D obj) {
return ("A & D");
}
public String run(A obj) {
return ("A & A");
}
}
class B extends A {
public String run(B obj) {
return ("B & B");
}
public String run(A obj) {
return ("B & A");
}
}
class C extends B {
}
class D extends B {
}
public class Test {
public static void main(String[] args) {
A aa = new A();
A ab = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println("1--" + aa.run(b));
System.out.println("2--" + aa.run(c));
System.out.println("3--" + aa.run(d));
System.out.println("4--" + ab.run(b));
System.out.println("5--" + ab.run(c));
System.out.println("6--" + ab.run(d));
System.out.println("7--" + b.run(b));
System.out.println("8--" + b.run(c));
System.out.println("9--" + b.run(d));
}
}
结果:
1--A & A
2--A & A
3--A & D
4--B & A
5--B & A
6--A & D
7--B & B
8--B & B
9--A & DProcess finished with exit code 0
分析:
1中 aa 在编译时取决于左边 A 的类型,所以包含了参数为 A、D 的 run 方法,而运行时传递给 run 方法的参数类型为 A,所以执行了 A 类中参数为 A 类型的 run 方法。
2中 aa 在编译时取决于左边 A 的类型,所以包含了参数为 A、D 的 run 方法,而运行时传递给 run 方法的参数类型为 C,而此时只支持参数为 A、D 的 run 方法,而 C 又继承自 B,B 继承自 A,所以执行了 A 类中参数为 A 类型的 run 方法。
3中 aa 在编译时取决于左边 A 的类型,所以包含了参数为 A、D 的 run 方法,而运行时传递给 run 方法的参数类型为 D,而此时恰巧支持参数为 A、D 的 run 方法,所以直接执行了 A 类中参数为 D 类型的 run 方法。
4中 ab 在编译时取决于左边 A 的类型,运行时为右边 B 的类型,所以编译时包含了参数为 A、D 的 run 方法,而运行时传递给 run 方法的参数类型为 B,所以对应的方法为 A 类中参数为 A 类型的 run 方法,而由于 ab 在运行时右侧的 B 类中重写了 A 类中参数为 A 类型的 run 方法,所以运行时最终执行了 B 类中重写的参数为 A 类型的 run 方法(所以类 B 中参数为 B 的 run 方法其实是 B 类特有的重载方法,而不是重写方法)。
5中 ab 在编译时取决于左边 A 的类型,运行时为右边 B 的类型,所以编译时包含了参数为 A、D 的 run 方法,而运行时传递给 run 方法的参数类型为 C,C 又最终继承自 A,所以对应的方法为 A 类中参数为 A 类型的 run 方法,而由于 ab 在运行时右侧的 B 类中重写了 A 类中参数为 A 类型的 run 方法,所以运行时最终执行了 B 类中重写的参数为 A 类型的 run 方法(所以类 B 中参数为 B 的 run 方法其实是 B 类特有的重载方法,而不是重写方法)。
6中 ab 在编译时取决于左边 A 的类型,运行时为右边 B 的类型,所以编译时包含了参数为 A、D 的 run 方法,而运行时传递给 run 方法的参数类型为 D,所以对应的方法为 A 类中参数为 D 类型的 run 方法(所以类 B 中参数为 B 的 run 方法其实是 B 类特有的重载方法,而不是重写方法)。
7中 b 在编译时取决于左边 B 的类型,运行时为右边 B 的类型,所以编译时包含了参数为 A、B、D 的 run 方法,而运行时传递给 run 方法的参数类型为 B,所以对应的方法为 B 类中参数为 B 类型的 run 方法(B 在编译时已经继承了 A 的方法)。
8中 b 在编译时取决于左边 B 的类型,运行时为右边 B 的类型,所以编译时包含了参数为 A、B、D 的 run 方法,而运行时传递给 run 方法的参数类型为 C,而 C 的第一父类是 B,此时恰巧 B 中有支持参数为 B 的 run 方法(所以不用再往上找),所以对应的方法为 B 类中参数为 B 类型的 run 方法。
9中 b 在编译时取决于左边 B 的类型,运行时为右边 B 的类型,所以编译时包含了参数为 A、B、D 的 run 方法,而运行时传递给 run 方法的参数类型为 D,所以对应的方法为 B 类中从 A 类继承来的参数为 D 类型的 run 方法。