编译类型、运行类型及多态的区别
Java的引用变量有两个类型,一个是编译时类型,一个是运行时类型,编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,会出现所谓的多态。
- 因为子类其实是一种特殊的父类,因此Java允许把一个子类对象直接赋值给一个父类引用变量,无须任何转型,或者被称为向上转型,由系统自动完成。
测试代码
-
Father类
package com.polymorphic; public class Father { public Father(){ System.out.println("Father的构造方法"); } public void doWork(){ System.out.println("Father Do Work!"); } }
-
Son类
package com.polymorphic; public class Son extends Father{ public Son(){ System.out.println("Son的构造方法"); } public void doWork(){ System.out.println("Son Do Work!"); } }
-
Test类
package com.polymorphic; public class Test { public static void main(String[] args) { Father child = new Son(); son.doWork(); } }
-
运行结果
在测试类中:
Father child = new Son();
在这里我们声明了一个Father的引用变量,指向了Son类的一个实例,我们可以得知child这个引用变量的Father类就是编译类型,而new Son()中的Son类就是运行类型。
-
由运行结果可知:程序在运行时,首先会调用父类的构造器,然后再调用子类的构造器,接下来:在编译过程中,就会自动检查引用变量child的编译类型中,是否包含doWork方法,很明显,在父类中有doWork方法(如果没有会报错),但是,在JVM运行时,由于在子类中覆盖了doWork方法,所以child实际运行时,是调用了子类的doWork方法,而不是父类的,这也就是多态的一种(运行时多态)。
-
什么是编译类型和运行类型呢?
引用变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法(意思是说:编写代码时,只能调用父类中具有的方法,如果子类重写了该方法,运行时实际调用的是运行时类型的该方法。程序在编译时,会在编译类型中检查是否具有所调用的方法,如果编写代码时,使用引用变量调用子类中的特有方法,或者调用了重载父类中的方法,而父类中找不到该方法,则会编译报错),因此,编写Java代码时,引用变量只能调用声明该变量所用类里包含的方法。与方法不同的是,对象的属性则不具备多态性。通过引用变量来访问其包含的实例属性时,系统总是访问它编译时类所定义的属性,而不是它运行时所定义的属性(属性无多态,方法具有多态)。
要访问子类中特有的方法和属性,在编写代码时,必须进行类型转换。
——以上摘自《疯狂Java讲义》
-
什么是多态?
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
在测试类中:
//编译时类型 引用变量 运行时类型 Father child = new Son();
这行代码编译时类型和运行时类型不一致(父类引用指向子类),同时Son继承了Father并且重写了Father的doWork方法,这就是所谓的多态。
-
代码演示方法具有多态,属性无多态
Class A
package com.polymorphic; public class A { String a = "This is A!"; public String getString(){ return this.a; } }
**Class B **
package com.polymorphic; public class B extends A{ String a = "This is B!"; public String getString(){ return this.b; } }
Test
package com.polymorphic; public class Test { public static void main(String[] args) { A a1 = new A(); System.out.println(a1.getClass()+" "+a1.getString()+" "+a1.a); B b1 = new B(); System.out.println(b1.getClass()+" "+b1.getString()+" "+b1.a); a1 = b1; System.out.println(a1.getClass()+" "+a1.getString()+" "+a1.a); b1 = (B)a1; System.out.println(b1.getClass()+" "+b1.getString()+" "+b1.a); A a2 = new B(); System.out.println(a2.getClass()+" "+a2.getString()+" "+a2.a); } }
运行结果
从这个结果可以看出类继承时,子类会覆盖与父类相同的属性。总结一点就是:对象访问变量看声明,访问方法看实际对象类型(new出来的类型)。也可以说:属性无多态,而方法具有多态。