多态
1、多态的基础是封装、继承
2、方法的多态:重载、重写
3、对象的多态
(1)前提:两个对象(类)存在继承关系
(2)一个对象的编译类型和运行类型可以不一致
(3)编译类型在定义对象时,就确定了,不能更改
(4)运行类型可以更改
(5)“=”的左方:编译类型;“=”的右方:运行类型
多态的向上转型
父类类型 父类引用名 = new 子类类型();
1、本质:一个子类对象直接赋给一个父类的引用变量,无需任何类型转换,即父类引用指向子类对象
2、在遵循访问权限的前提下,可以调用父类所有非重写成员,及子类所有重写成员,不能调用子类特有成员
3、编译阶段,编译类型决定可调用的成员,即能不能做,从顶级类向下查找,直到父类
4、运行阶段,运行类型决定具体实施,即怎么做,由创建的子类类型决定
Father father = new Son1();//编译类型:Father,运行类型:Son1
father = new Son2();//编译类型不变,运行类型:Son2
多态的向下转型
子类类型 子类引用名 = (子类类型) 父类引用名;
1、本质:一个父类对象赋给子类引用,并将父类对象强制转换成子类类型,即改变了运行类型
2、是强制转换父类的引用,而不是强制转换父类的对象实例
3、可以调用子类所有成员
4、向下转型是配合向上转型的,父类引用必须指向当前目标类型的对象,即父类引用指向对象的类型与要强制转换的类型一致
Father father = new Son1();
Son1 son1 = (Son1) father;
Son2 son2 = (Son2) father;
(1)Son2 son2 = (Son2) father; 不成立,运行时抛出 ClassCastException(强制类型转换异常)
(2)父类引用 father、子类引用 son1 指向同一个 Son1 对象,即向下转型后,父类引用仍存在
5、属性不能被重写,由编译类型决定调用的属性
6、比较操作符:instanceof,判断对象的运行类型是否为某类或某类的子类,返回 boolean 值
引用名 instanceof 类名
(1)若父类引用指向子类对象,在向下转型的过程中是安全的,编译阶段不会出错
(2)若父类引用指向父类对象,在向下转型的过程中是不安全的,编译阶段不会出错,但运行阶段会抛出 ClassCastException(强制类型转换异常),一般使用 instanceof 先验证,再向下转型
多态应用
1、多态数组(向上转型):数组定义时,编译类型为父类类型,允许保存的元素为子类类型
2、多态参数(向上转型):方法定义时,形参类型为父类类型,允许实参是形参的子类类型
动态绑定机制
1、调用对象方法时,该方法与该对象的运行类型(内存地址)绑定
2、调用对象属性时,不存在动态绑定机制,由编译类型决定调用的属性
3、当有多个重名函数时,在决定要调用哪个函数的过程中,首先按照参数类型进行匹配,即寻找在所有重载版本中最匹配的,然后才看变量的动态类型,进行动态绑定
理解方法调用
1、下面假设要调用 x.f(args),隐式参数 x 声明为类 C 的一个对象。下面是调用过程的详细描述
2、编译器查看对象的声明类型和方法名
(1)需要注意的是:有可能存在多个名字为 f 但参数类型不一样的方法
(2)例如,可能存在方法 f(int) 和方法 f(String)。编译器将会一一列举 C 类中所有名为 f 的方法和其超类中所有名为 f 而且可访问的方法(超类的私有方法不可访问)
(3)至此,编译器已知道所有可能被调用的候选方法。
3、接下来,编译器要确定方法调用中提供的参数类型
(1)如果在所有名为 f 的方法中
(2)存在一个与所提供参数类型完全匹配的方法,就选择这个方法。这个过程称为重载解析(overloading resolution)
(3)例如,对于调用 x.f("Hello"),编译器将会挑选 f(String),而不是 f(int)。由于允许类型转换,int 可以转换成 double,等等,所以情况可能会变得很复杂
(4)如果编译器没有找到与参数类型匹配的方法,或者发现经过类型转换后有多个方法与之匹配,编译器就会报告一个错误
(5)至此,编译器已经知道需要调用的方法的名字和参数类型
4、静态绑定、动态绑定
(1)静态绑定在程序编译阶段即可决定,而动态绑定则要等到程序运行时
(2)实例变量、private 方法、static 方法 / 变量、final 方法都是静态绑定
(3)如果是静态绑定的方法,编译器将可以准确地知道应该调用哪个方法
(4)与此对应的是,如果要调用的方法依赖于隐式参数的实际类型,那么必须在运行时使用动态绑定
(5)静态绑定的方法不可被子类重写
5、程序运行并且采用动态绑定调用方法时,虚拟机必须调用与 x 所引用对象的实际类型对应的那个方法
(1)假设 x 的实际类型是 D,它是 C 类的子类
(2)如果 D 类定义了方法 f(string),就会调用这个方法;否则,将在 D 类的超类中寻找 f(string),以此类推
(3)每次调用方法都要完成这个搜索,时间开销相当大
(4)因此,虚拟机预先为每个类计算了一个方法表(methodtable),其中列出了所有方法的签名和要调用的实际方法
(5)在真正调用方法的时候,虚拟机仅查找这个表就行了
(6)在前面的例子中,虚拟机搜索 D 类的方法表,寻找与调用 f(sting) 相匹配的方法,这个方法既有可能是 D.f(string),也有可能是 x.f(String),这里的 X 是 D 的某个超类
(7)这里需要提醒一点,如果调用是 super.f(param),那么编译器将对隐式参数超类的方法表进行搜索
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战