一,多态内容
向上转型,向下转型。
①向上转型(一般多态指的是这个)
指的是父类引用指向子类的实例。
father a=new son()
语法原理:静态绑定,和动态绑定。
属于静态绑定的有:成员变量,final、private、static所定义的类型都为静态绑定。
动态绑定为方法。
转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6554103.html
一:绑定
把一个方法与其所在的类/对象 关联起来叫做方法的绑定。绑定分为静态绑定(前期绑定)和动态绑定(后期绑定)。
二:静态绑定
静态绑定(前期绑定)是指:在程序运行前就已经知道方法是属于那个类的,在编译的时候就可以连接到类的中,定位到这个方法。
在Java中,final、private、static修饰的方法以及构造函数都是静态绑定的,不需程序运行,不需具体的实例对象就可以知道这个方法的具体内容。
三:动态绑定
动态绑定(后期绑定)是指:在程序运行过程中,根据具体的实例对象才能具体确定是哪个方法。
动态绑定是多态性得以实现的重要因素,它通过方法表来实现:每个类被加载到虚拟机时,在方法区保存元数据,其中,包括一个叫做 方法表(method table)的东西,表中记录了这个类定义的方法的指针,每个表项指向一个具体的方法代码。如果这个类重写了父类中的某个方法,则对应表项指向新的代码实现处。从父类继承来的方法位于子类定义的方法的前面。
动态绑定语句的编译、运行原理:我们假设 Father ft=new Son(); ft.say(); Son继承自Father,重写了say()。
1:编译:我们知道,向上转型时,用父类引用执行子类对象,并可以用父类引用调用子类中重写了的同名方法。但是不能调用子类中新增的方法,为什么呢?
因为在代码的编译阶段,编译器通过 声明对象的类型(即引用本身的类型) 在方法区中该类型的方法表中查找匹配的方法(最佳匹配法:参数类型最接近的被调用),如果有则编译通过。(这里是根据声明的对象类型来查找的,所以此处是查找 Father类的方法表,而Father类方法表中是没有子类新增的方法的,所以不能调用。)
编译阶段是确保方法的存在性,保证程序能顺利、安全运行。
2:运行:我们又知道,ft.say()调用的是Son中的say(),这不就与上面说的,查找Father类的方法表的匹配方法矛盾了吗?不,这里就是动态绑定机制的真正体现。
上面编译阶段在 声明对象类型 的方法表中查找方法,只是为了安全地通过编译(也为了检验方法是否是存在的)。而在实际运行这条语句时,在执行 Father ft=new Son(); 这一句时创建了一个Son实例对象,然后在 ft.say() 调用方法时,JVM会把刚才的son对象压入操作数栈,用它来进行调用。而用实例对象进行方法调用的过程就是动态绑定:根据实例对象所属的类型去查找它的方法表,找到匹配的方法进行调用。我们知道,子类中如果重写了父类的方法,则方法表中同名表项会指向子类的方法代码;若无重写,则按照父类中的方法表顺序保存在子类方法表中。故此:动态绑定根据对象的类型的方法表查找方法是一定会匹配(因为编译时在父类方法表中以及查找并匹配成功了,说明方法是存在的。这也解释了为何向上转型时父类引用不能调用子类新增的方法:在父类方法表中必须先对这个方法的存在性进行检验,如果在运行时才检验就容易出危险——可能子类中也没有这个方法)。
四:区分
程序在JVM运行过程中,会把类的类型信息、static属性和方法、final常量等元数据加载到方法区,这些在类被加载时就已经知道,不需对象的创建就能访问的,就是静态绑定的内容;需要等对象创建出来,使用时根据堆中的实例对象的类型才进行取用的就是动态绑定的内容。
以上文章解释了,
为什么a调用成员变量时只能调用到父类的成员变量。
为什么a在调用子类的新的成员变量,新方法时会编译错误(编译时检查声明的类型有没有该方法)
为什么a可以调用子类重写父类的方法。(运行时在实例中找该方法)
②向上转型的作用
把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。
代码的通用性。
举个栗子,肥🐻看好了:
没用多态:
class people{
public void play() {
System.out.print("生命在于运动");
}
}
class son extends people implements wash{
public void play() {
System.out.print("来踢球");
}
public void wash() {
System.out.print("来洗澡");
}
}
class student extends people{
public void play() {
System.out.print("来学习");
}
}//开始测试///
public class test {
public static void dowhat2(student s) {
s.play();
}
public static void dowhat3(son s) {
s.play();
}
public static void main (String args[]) {
student p2=new student();
son p3=new son();
dowhat2(p2);
dowhat3(p3);
//dowhat2(p3)会错的,类型不符合。
可以看出,每多一个类型,就要多增添(修改)一次dowhat方法。通用性很低。
用了多态后:
public class test {
public static void dowhat1(people s) {//只有一个方法噢
s.play();
}
public static void main (String args[]) {
people p=new son();
people p1=new student();
dowhat1(p);
dowhat1(p1);//在同一个方法,丢参数进去,让系统帮你识别实例类型,从而调用相应子类的方法。
}
}
②向下转型
多态里的强制性转换,将父类强制性转换成子类
为什么要用向下转型:
如果要用父类类型去调用子类的新的方法,新的成员。就要转换.
补充了向上转型的的不可。
一个典型例子便是标准库中的数据类型包装类:Integer类,Double类,Long类等,它们都继承自Number类,且它们都有一个方法叫做compareTo用于比较两个同样的类型。然而这个方法是这些子类通过实现Comparable接口来实现的,在Number类中并没有该方法的实现,因此若要通过Number类型变量来使用compareTo方法,就要先将Number类转换成子类的对象。
参考这个文章:https://blog.youkuaiyun.com/FengGLA/article/details/61502423
向下转型的语法
class people{
}
class son extends people implements wash{
public void play() {
System.out.print("来踢球");
}
public void wash() {
System.out.print("来洗澡");
}
}
class student extends people{
public void play() {
System.out.print("来学习");
}
}
public static void main (String args[]) {
people p=new son();//people的子类
people p1=new student();//people的子类
((son)p).play();//输出来踢球
((student)p1).play();//输出来学习
//p.play();编译错误,向上转型不能调用子类的新方法,如果要调用1只能用强制性转换
//p1.play();编译错误,向上转型不能调用子类的新方法,如果要调用1只能用强制性转换
}
结果:
**
补充!!!但是注意
向下转型只能用于
-——当实例是子类时:
father two =new son();
((son)two).wash()
son one =(son)new people();//编译通过,运行异常
因为相当于把one变成son类型,但实际上还是people实例。
练习:
答案ED
答案BC
③类和接口的多态都是一样的。真的。语法都一样的- -。就是把接口interface看成父类。实现了该接口的类,看作子类。
向上转型和向下转型都使用噢!
栗子:向下转型:
interface wash{
void wash();
}
class son extends people implements wash{
public void play() {
System.out.print("来踢球");
}
public void wash() {
System.out.print("来洗澡");
}
}
public static void main (String args[]) {
wash w=new son();
w.wash();
((son)w).play();
w.play//编译错误
}
因为父类不能用子类的新方法。转换的形式向上面那样
只有((son)w).play()才能算是转换噢!!!!
**