1. 多态
多态这种特性简而言之就是用父类型别的引用指向其子类的实例,然后通过父类的方法调用实际子类的成员函数. 先抛结论: 个人理解的所谓多态, 本质上就是重写机制加上向上转型, 重写就是说子类的同名同参数的方法可以覆盖父类的方法, 子类的向上转型消除了多余方法, 就成了多态.
先给出一个多态非常简单的例子:
Father father = new Child();
这里例子中我们new一个Child实例将它丢给father这个接口, 当调用没有被重写的方法时, JVM执行父类方法, 当调用被Child重写过的方法时, 则执行子类方法.
2. 多态的实现
首先先明确一件事, 多态=重写+向上转型, 所以下面关于的多态的原理其实就是重写的原理.
对C++了解的同学应该知道C++里面有虚函数这个东西, C++为了实现多态, 在就在实例对象里面内嵌了虚函数表vtable, 这个虚函数表, 其实就是一个存储了一堆方法指针的表, 来指定调用的函数, 实现方法分派. 如果没有virtual关键字, 那么这个函数会在vtable中保存两份, 父类的函数和子类的函数都会保存. 用父类引用调用父类函数, 子类引用调用子类函数.
Java的vtable和C++大同小异, 最大的区别在于Java的vtable是在类加载时动态构建的, 大致的逻辑是这样的:
- 一开始继承类的vtable中的指针完全指向父类的方法, 也就是说一开始子类的vtable就是父类的vtable
- JVM遍历子类的所有方法, 找到所有被public或protected关键词修饰, 并且没有被static或final修饰的方法, 判断其方法签名是否与父类一致, 如果一致说明发生了重写, 那么将子类的原本指向父类的方法指针修改为指向自己的方法.
- 如果找出来的方法签名不一致, 说明子类自己定义了新方法, 那么就把vtable长度增加1, 并将指针指向新方法
经过以上步骤, vtable构建完成, 这张表里面分别包含了父类方法(没重写的)和子类方法(重写的), 剩下的事情就顺理成章了, 程序在调用父类引用方法的时候, JVM会通过vtable里的指针来调用方法, 这就是多态.
3. 多态的好处
多态可以实现接口复用, 隐藏实现细节, 降低代码的复杂度. 策略模式就是个最简单的例子, 策略模式让一个接口实现多种类, 但是反映在代码里面真实使用的就是父类引用Father, 这样不仅封装了内部细节, 使代码更简洁. 还增加了可维护性, 在需要这个接口或者父类实现不同功能的时候只需要将不同的子类向上转型为Father就行了, 简洁明了.
这种简化分层其实就是解耦的思想, 解耦的最终目的就是为了使不同功能的代码放在不同地方, 所谓高内聚低耦合, 从而实现强大的可维护性, 提升效率. 具体来看, 不论是多态也好, 依赖注入也好, XML配置也好, 最终目的就是将所有配置和赋值放在一起, 而把核心代码放在另一个地方, 当需要修改时, 只需要修改配置文件或者实现不同的接口就可以实现不同功能
接口复用让模块化或者分层变得简单, 在写一个大型项目的时候, 一般来说都需要很多模块互相调用. 作为一个模块的开发者我要是不想经常让别人修改代码, 我就得保持自己写的接口不变, 来修改下面的实现类.
进一步思考, 这个时候想想什么叫做控制反转就显而易见了, 目的还是为了进一步降低耦合度. 对于一个组件来说, 开发者甚至不希望使用者自己来实现接口, 而是让使用者加个注解, 我自己来帮你实现, 优点当然是显而易见的, 组件的实现类甚至连名字都可以自由更换.