java面向对象三大特性之多态---编译时多态和运行时多态详解
原创颺灏 发布于2019-04-01 22:17:55 阅读数 494 收藏
一.引言
说到重载和重写,大家可能都知道。它们都是多态性的体现,那么说什么是多态呢?多态是指允许不同子类型的对象对同一行为作出不同的响应。例如在生活中,比如跑的动作,小猫、小狗和大象,跑起来是不一样的。再比如飞的动作,昆虫、鸟类和飞机,飞起来也 是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。多态描述的就是这样的状态。
多态性分为编译时的多态性和运行时的多态性。方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的东西,要实现运行时多态需要做以下两件事情:
1. 方法重写(子类继承父类并重写父类中已有的或抽象的方法);
2. 对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。
那么到底如何判定是编译时多态还是运行时多态呢?它们之间的区别到底是什么呢?如果在编译时能够确定执行多态方法中的哪一个,称为编译时多态,否则称为运行时多态。下面我们就从重载和重写两方面来认识一下这两种多态机制。
二.编译时多态
编译时多态,也可以叫做静态多态性,静态绑定。下面请看一个例子:
方法重载就是在同一个类中,出现了多个同名的方法,他们的参数列表(方法签名)不同 (参数列表的个数不同,参数列表的数据类型不同,参数列表的顺序不同)。来看下面一个代码:
public class addCalculate {
public static void main(String[] args) {
// 下面是针对求和方法的调用
int sum1 = add(1, 2);
int sum2 = add(1, 2, 3);
double sum3 = add(1.2, 2.3);
// 下面的代码是打印求和的结果
System.out.println("sum1=" + sum1);
System.out.println("sum2=" + sum2);
System.out.println("sum3=" + sum3);
}
// 下面的方法实现了两个整数相加
public static int add(int x, int y) {
return x + y;
}
// 下面的方法实现了三个整数相加
public static int add(int x, int y, int z) {
return x + y + z;
}
// 下面的方法实现了两个小数相加
public static double add(double x, double y) {
return x + y;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
根据实际参数的数据类型、个数和次序,Java在编译时能够确定执行重载方法中的哪一个。
除了重载,重写也表现出两种多态性。当一个对象的引用指向的是当前对象所属类的对象时,为编译时多态.其他则为运行时多态。如下:
public class Father {
public void say() {
System.out.println("我是爸爸");
}
}
public class Sun extends Father{
public void say() {
System.out.println("我是儿子");
}
public static void main(String[] args) {
Sun sun = new Sun();
Father father = new Father();
sun.say();
father.say();
}
}
如main方法中所书写的那样,sun的引用指向的就是Sun类的对象,故在编译时期就可以确定要执行sun类中的say()方法,故属于编译时多态。
三.运行时多态
运行时多态一个最显著的例子就是子类的上转型对象,即父类引用指向之类对象,调用的方法只能是父类下包含的方法(指向子类的那个父类引用),执行的结果是子类重写以后的。例如:
public class Father {
public void say() {
System.out.println("我是爸爸");
}
}
public class Sun extends Father{
public void say() {
System.out.println("我是儿子");
}
public static void main(String[] args) {
Father father = new Sun();
father.say();
}
}
执行结果为:
从程序的运行结果我们可以发现,它执行的是子类的方法。为什么呢?这是因为Java支持运行时多态,当执行father,say()时,在编译时期,首先会去查看father类里面有没有这个方法,如果没有的话向上继续查找,直到找到Object类如果还没有的话就报错,如果有的话,到运行阶段,再去看一下子类中有没有覆盖该方法,如果覆盖了,则执行子类覆盖的方法。如果没有则执行父类中原本的方法。
四.运行时多态的陷阱
看下面代码:
public class Father {
public void say() {
System.out.println("我是爸爸");
}
}
public class Sun extends Father{
public void say() {
System.out.println("我是儿子");
}
public void write() {
System.out.println("我是子类新增的方法");
}
public static void main(String[] args) {
Father father = new Sun();
father.say();
///编译时报错The method write() is undefined for the type Father
father.write();
}
}
在编译father.write();这行时,编译时报错The method write() is undefined for the type Father因此,当父类引用指向子类对象时候,父类只能执行那些在父类中声明、被子类覆盖了的子类方法(如上文中的say()),而不能执行子类增加的成员方法。
public class Father {
String str = "father";
public void say() {
System.out.println("我是爸爸");
}
}
public class Sun extends Father{
String str = "sun";
public void say() {
System.out.println("我是儿子");
}
public static void main(String[] args) {
Father father = new Sun();
System.out.println(father.str);
}
}
运行结果如下图所示
所以,当子类和父类有相同属性时,父类还是会执行自己所拥有的属性,若父类中没有的属性子类中有,当父类对象指向子类引用时(向上转型),在编译时期就会报错
看下面代码:
public class Father {
public static void say() {
System.out.println("我是爸爸");
}
}
public class Sun extends Father{
public static void say() {
System.out.println("我是儿子");
}
public static void main(String[] args) {
Father father = new Sun();
father.say();
}
}
执行结果如下
从上图的程序运行结果我们可以看到,father.say()语句执行的是Father类中的say方法。所以对于static方法还是会执行父类中的方法
这是由于在运行时,虚拟机已经认定static方法属于哪个类。“重写”只能适用于实例方法,不能用于静态方法。对于静态方法,只能隐藏,重载,继承。
子类会将父类静态方法的隐藏(hide),但子类的静态方法完全体现不了多态,就像子类属性隐藏父类属性一样,在利用引用访问对象的属性或静态方法时,是引用类型决定了实际上访问的是哪个属性,而非当前引用实际代表的是哪个类。因此,子类静态方法不能覆盖父类的静态方法。 而fafther的引用类型为Father,因此会执行Father的静态方法。
五.总结
(1)多态是指不同子类型的对象对同一行为作出不同的响应。
(2)多态性分为编译时的多态性和运行时的多态性。方法重载实现的是编译时的多态性,而方法重写(实现的是运行时的多态性。
(3)对于运行时多态,特别注意,父类引用指向子类对象,在调用实例方法时,调用的是子类重写之后的,并且不能调用子类新增的方法,对于属性和static方法来说,还是执行父类原有的。