这次我们来看一个Java中经典的问题。我们都知道面向对象三大特征:封装、继承、多态
我们先来看一个小小的程序
class A{
public void fun1(){
System.out.println("A1");
}
public void fun2(){
this.fun1();
}
}
class B extends A{
public void fun1(){
System.out.println("B1");
}
public void fun3(){
System.out.println("B3");
}
}
public class FactoryTest{
public static void main(String []args){
A a = new B();
a.fun1();
a.fun2();
}
}
说实话,第一眼看到这个程序,我的想法是A a = new B();,这很明显是父类型引用指向了一个子类型对象,我们很容易想清楚在Java虚拟机中,应该是有一个B的类型被在堆内存开辟了出来,所以第一项a.fun1();应该是输出B1,这是没有异议的,但我们重点关注一下,第二项a.fun2();会输出些什么,让我们先来看看答案吧,毕竟实践出真知。
B1
B1
没错,我们的答案正是如此,其实在一开始我会以为报错,但我忽视了一点,B是继承了A的,Java中子类继承父类的数据:除私有的、构造方法以外的所有数据,所以fun2()方法没有在B中重写,这就意味着他只是正常继承这一方法,自然也可以调用。而调用的是this.fun1();,这代表着调用本对象的fun1(),那自然就是要输出B1了。
那么如果我们把a.fun2()改为a.fun3()会发生什么呢?我们来一起试试。
class A{
public void fun1(){
System.out.println("A1");
}
public void fun2(){
this.fun1();
}
}
class B extends A{
public void fun1(){
System.out.println("B1");
}
public void fun3(){
System.out.println("B3");
}
}
public class FactoryTest{
public static void main(String []args){
A a = new B();
a.fun1();
a.fun3();
}
}
执行结果就是
Error:(31, 10) java: 找不到符号
符号: 方法 fun3()
位置: 类型为classANDobject.A的变量 a
我们发现出错了,那这是为什么呢?明明对象是B类型,怎么就没法用呢?
话已至此,那就让我们来聊一聊Java的二三事。
Java程序永远都氛围编译阶段和运行阶段,先分析编译阶段再分析运行阶段,编译无法通过是根本不能运行的。编译阶段编译器检查a这个引用的类型为A,第一个程序中由于A.class字节码中有fun2()方法,所以编译通过了。而第二个程序中A并没有fun3()方法,所以他没有通过编译。这个过程我们称为静态绑定,编译阶段绑定。只有静态绑定成功之后才有后续的运行,用通俗的话讲。
编译都编译不过去,你还想运行?!
而在第一个程序运行阶段,JVM堆内存当中真是创建的对象是B对象,那么以下程序在运行阶段一定会调用B对象的fun1()与fun2()方法,此时发生了动态绑定,运行阶段绑定。
接着我们来聊一聊继承中不可避免的------多态,向上转型与向下转型。
- 向上转型(upcasting):子类型->父类型,又叫自动类型转换
- 向下转型(downcasting):父类型->子类型,又叫强制类型转换(要加强制类型转换符)
上面A a = new B();我们的已经看到了向上转型,Java中允许这种语法:父类型引用指向子类型对象,但什么是向下转型呢?我们先会看第二个程序,他报错了,但我们仍然希望,a可以调用fun3()函数,毕竟它实际开辟的对象是B啊!
好我们在a.fun3();前加一句,B b = (B)a;我们再来重试试这段程序。
class A{
public void fun1(){
System.out.println("A1");
}
public void fun2(){
this.fun1();
}
}
class B extends A{
public void fun1(){
System.out.println("B1");
}
public void fun3(){
System.out.println("B3");
}
}
public class FactoryTest{
public static void main(String []args){
A a = new B();
a.fun1();
B b = (B)a;
b.fun3();
}
}
结果如下。
B1
B3
哦!!!我们发现了,他成功了,问题来了。
Q:什么时候向下转型?
A:当调用的方法是子类型特有的,在父类中不存在,必须向下转型。
当然,转型,需要两种类型之间要有继承关系,没有的话,就会报错。
所以我们要有一个确保我们没有出错的语句。我们再来重新写一下程序。
class A{
public void fun1(){
System.out.println("A1");
}
public void fun2(){
this.fun1();
}
}
class B extends A{
public void fun1(){
System.out.println("B1");
}
public void fun3(){
System.out.println("B3");
}
}
class C extends A{
public void fun1(){
System.out.println("C1");
}
public void fun3(){
System.out.println("C3");
}
}
public class FactoryTest{
public static void main(String []args){
A a = new B();
a.fun1();
if(a instanceof B) {
B b = (B) a;
b.fun3();
}
else if(a instanceof C){
C c = (C) a;
c.fun3();
}
else{
System.out.println("我永远喜欢結城明日奈");
}
}
}
instanceof结果为true/false,代表前者是否为后者的一个实例,这样可以帮助我们避免一些错误转型。我们来看一看结果吧。
B1
B3
没错,结果就是永远轮不到我喜欢結城明日奈。