不少人老是喜欢,或者说错误地将JAVA中的多态理解得很复杂,最多见的错误说法就是所谓“方法的多态”,他们会给出相似下面的例子来佐证“多态是指方法的多态”:java
//Enginner和Mechanic是Employee的子类,构造函数参数均为月薪salary Employee a=new Enginner(100); Employee b=new Mechanic(100); //getAnnualSalary是Employee类的方法,用于计算并返回年薪 System.out.println(a.getAnnualSalary());//输出1500,Enginner年薪为15倍月薪 System.out.println(b.getAnnualSalary());//输出1300,Mechanic年薪为13倍月薪
从结果上看,a、b都是Employee类对象变量,然而对a调用getAnnualSalary()返回的是15*salary,对b调用getAnnualSalary()却返回了13*salary,好像的确是所谓“方法的多态”,毕竟对同一类对象变量调用同一个方法,内部实现方式却出现不一样了嘛。基于这样的想法,甚至有一些人将多态扩展到了更普遍、更复杂的状况,好比下面这种,连泛型都算进了多态中:数组
那么,多态真的是有那么多种状况吗?真的是只要方法名相同,而参数或者内部实现方式不一样,就要当作是多态吗?不不不,这种说法纯属扯淡,JAVA中的多态有且只有一种状况:对象变量是多态的。这个理解相当重要,能够说对于多态的概念,要记住的就是这个点。可是,为何在上面的例子中,对a和b调用同一个方法,会有不一样的效果呢?注意,这是方法调用的知识范畴,只是刚好和多态相关罢了。下面咱们就来理清一下多态与方法调用的知识。函数
JAVA中的多态是由继承机制带来的,正是由于有继承机制,因此才存在多态。简单来讲,多态的原由就是JAVA中容许一个父类对象变量引用一个子类对象(至于为何咱们以后会说):学习
//Son是Father的子类 Father variable=new Son(); //variable是一个Father类对象变量,但它实际引用的对象倒是Son类对象
因为父类对象变量能够引用子类对象,因此当咱们看到一个A类对象变量时,咱们不能一口咬定它所引用的对象就是A类对象,它也有可能引用B类对象、C类对象……只要它引用的对象是A类的子类对象就行。这就是多态:对象变量实际引用的对象的类型不必定是对象变量声明的类型。spa
可是单纯的多态并没卵用,我令Employee类对象变量a引用了一个Enginner对象,而后呢,即使我在Enginner中重写了getAnnualSalary以返回15薪,在对a调用getAnnualSalary时依然返回12薪吗?(假设Employee类中getAnnualSalary返回12*salary)那有什么意义?翻译
因此实际上,多态的存在,必需要有方法调用时的动态绑定支持才有意义。所谓方法调用的动态绑定,就是:虚拟机会调用与变量所引用的实际类型最匹配的那个方法。code
举例来讲,Employee类的getAnnualSalary返回12*salary,Enginner类重写了该方法以返回15*salary,那么当出现下述状况时:对象
Employee a=new Enginner(100); int annualSalary=a.getAnnualSalary();
虚拟机会先判断变量a所引用的对象其实是什么类型(此例实际类型为Enginner),而后查看其实际类型是否重写了该方法(此例Enginner重写了Employee中的getAnnualSalary方法),若是是则调用其实际类型中的该方法(此例也即调用Enginner类中返回15*salary的getAnnualSalary),不然调用a声明的类型(即Employee)中的该方法。blog
经过多态+动态绑定,咱们就能够快速地实现一些效果。好比说写一个抽象类List,声明一个get方法以获取列表中指定元素,声明一个set方法以设置列表中指定元素,而后实现一个非抽象子类LinkedList,内部采用链表结构存储列表,再实现一个ArrayList,内部采用数组结构存储列表。这样一来,咱们就能够利用多态+动态绑定这样写代码:继承
List a=new ArrayList(); oldValue=a.get(i); a.set(i,newValue);
若是咱们想要使用一个能够良好支持随机访问的列表,咱们就能够像上面这样写,即令a引用一个ArrayList对象,若是哪一天咱们但愿此处改用使用良好支持动态增减的列表了,只须要将
List a=new ArrayList();
改成:
List a=new LinkedList();
便可,而其他代码不须要改动。经过方法的动态绑定,对get和set的调用都将自动成为对LinkedList类中的方法调用。这样一来,改变列表的实际存储结构就成了一个很简单的事情。
此外,多态+动态绑定还能够在“只关注通用方法”时起到简化代码的效果。什么意思呢?举例来讲就是Enginner和Mechanic有各自不一样的,在Employee类基础上新增的方法。可是咱们在统计员工薪水时,并不想关注它们各自独有的东西,只想关注一样做为Employee都会有的年薪。那我就能够将各个Enginner、Mechanic都放进一个Employee数组中,而后遍历该数组,对每一个元素调用getAnnualSalary并输出,而不用为Enginner创个数组遍历一遍,再对Mechanic创个数组遍历一遍。
固然,多态+动态绑定还有许多其余用途,尤为是在JAVA的各集合类应用上,此处不予细谈。
若是说动态绑定是解决了多态的方法调用问题,那么静态绑定就是为了快速实现(方法)重载机制。所谓重载机制就是指在JAVA中,容许一个方法的名字与已存在的另外一个方法相同,只要这两个方法的参数个数或类型不一样便可。这种多个方法名字相同、参数不一样的状况,就是方法重载。此处所说的“方法”也能够是构造器,所以这种机制叫作:重载。
要想实现重载,就得在调用方法时,根据调用时所给的参数决定到底调用哪一个方法。可是到底该何时肯定这件事呢?在JAVA中,这个确认步骤在编译器将源代码翻译为字节码时肯定,也即由编译器javac根据方法调用时所给的参数个数、类型来肯定实际该调用哪一个方法,从而实现重载。由于是在编译时肯定的,因此这个绑定过程就是静态绑定。
可是须要注意的是,静态绑定并不算真正的“绑定”,它实际上是一个筛选。什么意思呢?举例来讲,假设Employee类的getAnnualSalary还有一个带参数的版本:getAnnualSalary(double bonusRate),即给定一个“奖金比例”来计算年薪,那么当对一个Employee类对象变量a调用getAnnualSalary()时,编译器会先进行静态绑定,即筛选,从而肯定此处的方法调用不多是带参数的版本,但有多是Employee类的该方法,也有多是Enginner或Mechanic类的该方法,通过静态绑定后,剩下了三种可能,再由虚拟机在运行时经过动态绑定肯定真正调用的方法。
其实重载也能够作成让虚拟机来作的事情,可是经过编译器的静态绑定筛选掉一部分方法,就能够令虚拟机在肯定实际调用方法时减小一些工做量,只关注于动态绑定的可能方法上。因此说静态绑定是为了快速实现重载。
有关多态、方法调用的相关知识固然还有许多细节,好比一个方法x(int)和重载的方法x(double),在调用x(3)时既能够是调用x(int),也能够是调用x(double),到底选哪一个?为何重载不容许仅仅返回类型不一样?不过这些细节问题并非本文想要讨论的东西,本文要说的基本上就是上面那些提纲挈领的内容。
总的来讲,在学习JAVA多态时最重要的点就是要明白多态就是指对象变量的多态,不要去把多态这个概念复杂化。至于所谓“方法的多态”,其实就是方法调用的静态绑定(筛选)和动态绑定。