说到重载和重写,大家可能都知道。但是如果问你“编译时多态”和“运行时多态”,那么很多人可能会有一些些小小的蒙圈。
其实,这也没有啥好蒙圈的,因为: 重载都是编译时多态,而重写表现出两种多态性,当对象引用本类实例时,为编译时多态,否则为运行时多态。
怎么判定是编译时多态还是运行时多态呢?
如果在编译时能够确定执行多态方法中的哪一个,称为编译时多态,否则称为运行时多态。 |
下面我们就从重载和重写来认识一下这两种多态机制。
1.编译时多态(也可以叫做静态多态性)
在学习重载的时候我们知道, 重载要求方法名相同,参数和返回值随便改。这些都是实实在在已经确定了的事情,因此重载都是编译时多态。根据实际参数的数据类型、个数和次序,Java在编译时能够确定执行重载方法中的哪一个。
除了重载,上文还说到,重写也表现出两种多态性,当它引用本类实例的时候也是编译时多态。
举个例子:
public class Test {
public static void main(String[] args) {
Person p = new Person(); //对象引用本类实例
Man m = new Man(); //编译时多态,执行Person类的toString()
System.out.println(p.toString());
System.out.println(m.toString()); //编译时多态,执行Man类的toString()
}
}
class Person{
public String toString() {
String name = "this is Person class";
return name;
}
}
class Man extends Person{
public String toString(){
String name = "this is Man class";
return name;
}
}
运行结果如下:
2.运行时多态
运行时多态性主要运用到动态绑定。从它的名字上我们就可以知道,这玩意就是只有在运行时才能动态的确定调用的是哪一个函数。
上文我们说到,重写有两种表现,当对象引用本类实例的时候表现为编译时多态,而与之相反的,当对象引用的不是本类实例的时候,那就表现为运行时多态啦。
举个例子:
public class Test {
public static void main(String[] args) {
A b=new B();
A c=new C();
A d=new D();
D d2=new D();
b.say();
c.say();
d.say();
d2.say(1);
d2.say("");
}
}
abstract class A{
public void say() {
System.out.println("this is A class");
};
}
class B extends A{
@Override
public void say() {
System.out.println("this is class b");
}
}
class C extends A{
@Override
public void say() {
System.out.println("this is class c");
}
public void toString(boolean b){
System.out.println("this is class c boolean");
}
}
class D extends B{
@Override
public void say() {
System.out.println("this is class d");
}
public void say(int i){
System.out.println("this is class d int");
}
public void say(String string){
System.out.println("this is class d string");
}
}
运行结果如下:
从程序的运行结果我们可以发现,它们执行的都是子类的方法。为什么呢?
对于
A b=new B();
b.say();
Java支持运行时多态,意为b.say()实际执行b所引用实例的say(),究竟执行的是A类还是B类的方法,运行时再确定。如果B类声明了say()方法,则执行B类的方法;否则执行A类的say()方法。
寻找b.say()匹配执行方法的过程如下图所示。
程序运行时,Java从实例所属的类(new 类)开始寻找匹配的方法执行,如果当前类中没有匹配的方法,则沿着继承关系逐层向上,依次在父类或各祖先类中寻找匹配方法,直到Object类。
因此,父类对象只能执行那些在父类中声明、被子类覆盖了的子类方法(如上文中的say()),而不能执行子类增加的成员方法。
看完上面这些,大家也就大致了解了运行时多态,说到底,运行时多态就是只有在运行的时候才能确定执行的是哪一个类的方法。
对于运行时多态来说,对象的调用只能限于编译时类型的方法,如果调用运行时类型方法则会报错。例如:Animal a=new Dog();对象a的编译时类型为Animal,运行时类型为dog。注意:编译时类型一定要为运行时类型的父类(或者同类型)。而对于语句:Dog d=(Dog)a。将d强制声明为a类型,此时d为Dog(),此时d就可以调用运行时类型。注意:a和d指向同一对象。
引申:如果我将A类和B类中的say()改为static的,那么结果是否会是一样的呢?
如下:
public class Test {
public static void main(String[] args) {
A b=new B();
b.say();
}
}
abstract class A{
public static void say() {
System.out.println("this is A class");
};
}
class B extends A{
public static void say() {
System.out.println("this is class b");
}
}
程序运行结果会是什么样的呢?
按照上文中的说法,如果B类中有say()方法, 那么就会执行B中的方法,是这样的吗?
我们执行以下程序看看。
从上图的程序运行结果我们可以看到,b.say()语句执行的是A类中的say方法。
上文的程序中子类B会隐藏父类A的属性,而 A b = new B() 表示“先声明一个A 类的对象b,然后用B类对b进行实例化”,即引用类型为A类,实际代表的是B类。因此,访问的是A的属性及静态方法,详细解释如下。
所谓静态,就是在运行时,虚拟机已经认定此方法属于哪个类。“重写”只能适用于实例方法,不能用于静态方法。对于静态方法,只能隐藏,重载,继承。
子类会将父类静态方法的隐藏(hide),但子类的静态方法完全体现不了多态,就像子类属性隐藏父类属性一样,在利用引用访问对象的属性或静态方法时,是引用类型决定了实际上访问的是哪个属性,而非当前引用实际代表的是哪个类。因此,子类静态方法不能覆盖父类的静态方法。 而b的引用类型为A,因此会执行A的静态方法。
总之,父类中属性只能被隐藏,而不能被覆盖;而对于方法来说,方法隐藏只有一种形式,就是父类和子类存在相同的静态方法。
好啦,以上就是关于编译时多态和运行时多态的相关知识总结啦,如果大家有什么不明白的地方或者发现文中有描述不好的地方,欢迎大家留言评论,我们一起学习呀。
Biu~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~pia!