Java父类引用指向子类,多态,动态绑定与静态绑定

这次我们来看一个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

没错,结果就是永远轮不到我喜欢結城明日奈。
在这里插入图片描述
好了,这篇博客就到这里了,求赞求收藏,如果我有什么错误的话,欢迎大家指点,当然你对这篇博客还满意的话,希望大家点个关注,这是我继续下去的最大动力了。

原创文章 5 获赞 3 访问量 369

猜你喜欢

转载自blog.csdn.net/pige666/article/details/106114508