Java多态体现在两个方面,一是方法多态,二是对象多态。方法多态就是方法重载与方法重写,这点我们就放下不说,我们主要讲对象多态。
对象多态
对象多态的前提是继承关系。拥有继承关系的两个类,相互之间才会有多态的机制出现。另外,多态机制实际上就是java中静态绑定与动态绑定两个机制综合影响的结果。
静态绑定
所谓静态绑定,就是在编译时,编译器就确定了调用哪个方法,使用哪个变量等等。后续在程序实际运行时不再发生改变。
一般来说对于成员变量以及private、static、final或者构造器方法,java采用静态绑定策略。由编译器决定调用对象,运行时不再改变。具体可以看后面的案例
动态绑定
与静态绑定相对的,java在处理成员方法时(没有private,static,final,构造器),采用动态绑定策略。即编译器无法确定具体调用哪个对象的方法,留下调用点之后,由jvm在运行时动态决定。
由于jvm的实现逻辑,JVM会根据对象的运行时类型来查找对应的方法。因此会出现动态绑定的特点(其实是人为设计的)
当父类中的一个方法只有在父类中定义而在子类中没有重写的情况下,才可以被父类类型的引用调用; 对于父类中定义的方法,如果子类中重写了该方法,那么父类类型的引用将会调用子类中的这个方法,这就是动态绑定。
具体案例
class A{
public int i = 10;
public int sum(){
return getI()+10;
}
public int sum1(){
return i +10;
}
public int getI(){
return i
}
public int sumA(){
return 2*i
}
}
class B extends A{
public int i = 20;
public int sum(){
return getI()+10;
}
public int getI(){
return i
}
public int sumB(){
return 2*i;
}
}
成员变量的静态绑定
A a = new B()
System.out.println(a.i) //输出10
这个结果表明,子类的对象(由父类的引用handle)调用到的是父类的成员变量。所以必须明确,运行时(动态)绑定针对的范畴只是对象的方法。
向上转型
向上转型之后,运行时JVM判定a的运行类型为class B。因此a按照class B的访问权限处理,即class A,B的方法(除了private,static,final,构造器)都可以访问到,当然要遵守就近原则。但是,由于编译器语法问题,想要访问class B特有方法,编译器会报错。例如下面的sumB方法。因为编译器是静态绑定的,此时a的编译类型为class A,因此不能越界访问sumB
A a = new B()
a.sumA()//可以访问
a.sum()//该方法被覆盖了,所以访问B类,返回30
a.getI()//该方法覆盖,访问B类,返回20
a.sumB()//错误,无法访问,编译器报错
a.sun1()//B类中没有该方法,则向之前章节里说的那样,逐级向上查找,找到A类中的方法
下面这张图更加直观,使用父类引用来指向子类时,子类特有的方法不能被访问(编译器不允许)。剩下的方法(除了private,static,final,构造器),JVM按照子类的标准去访问,遵循就近原则。
![[Pasted image 20240827124945.png]]
向下转型
A a = new B()
B b = (B)a
有点类似于强制类型转化,转化完之后,编译器就认为b是class B类型的,就允许调用class B的特有方法了。
转载:Java把类型信息和函数信息分开放。Java中在继承以后,子类会重新设置自己的虚拟函数表,这个虚拟函数表中的项目有由两部分组成。从父类继承的虚拟函数和子类自己的虚拟函数。
虚拟函数调用是经过虚拟函数表间接调用的,所以才得以实现多态的。