java多态复习
最近在复习java基础,在多态上面有一些不太明白的地方,故记录下来供自己时时复习查阅巩固。
概述
我们都知道面向对象有三大特性,封装,继承,多态。封装很好理解,就是对于数据的保护机制,对外部隐藏内部的实现细节,仅仅暴露访问方法,这样的好处在于对于业务来说不用关心实现细节,而当实现细节需要改变时,也不会影响外部业务,可以说,封装是解耦的基础,也是面向对象思想的缩影。
继承和多态往往会把它们放在一起讲,继承是为了重用父类并对父类进行扩展,正式因为有了继承,才导致了多态的出现,那么多态到底定义是什么呢?所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
什么意思呢?比如有一个汽车类Car,车库里面有奔驰Benz、宝马BMW、比亚迪BYD,那么可以有如下构建方法
Car a = new Benz();
Car b = new BMW();
Car c = new BYD();
我们事前并不知道这辆车是什么车,只有当它开出来我们才能知道,这就是多态——我们只有在运行的时候才会知道引用变量所指向的具体实例对象。
向上转型
要理解多态就要知道什么叫向上转型,在上述例子中,如果只是像如下创建
Benz a = new Benz();
那就只是一个单纯的实例化了一个Benz对象,但是如果是这样呢?
Car a = new Benz();
我们可以看到,我们定义了一个Car的引用,指向了Benz实例对象,可以这样的原因是Benz继承于Car,所以Benz可以转型为Car,这个过程就叫做向上转型。这样做存在一个非常大的好处,在继承中我们知道子类是父类的扩展,它可以提供比父类更加强大的功能,如果我们定义了一个指向子类的父类引用类型,那么它除了能够引用父类的共性外,还可以使用子类强大的功能。
但是向上转型存在一些缺憾,那就是它必定会导致一些方法和属性的丢失,而导致我们不能够获取它们。所以父类类型的引用可以调用父类中定义的所有属性和方法,对于只存在与子类中的方法和属性无法调用。
public class Wine {
public void fun1(){
System.out.println("Wine 的Fun.....");
fun2();
}
public void fun2(){
System.out.println("Wine 的Fun2...");
}
}
public class JNC extends Wine{
/**
* @desc 子类重载父类方法
* 父类中不存在该方法,向上转型后,父类是不能引用该方法的
* @param a
* @return void
*/
public void fun1(String a){
System.out.println("JNC 的 Fun1...");
fun2();
}
/**
* 子类重写父类方法
* 指向子类的父类引用调用fun2时,必定是调用该方法
*/
public void fun2(){
System.out.println("JNC 的Fun2...");
}
}
public class Test {
public static void main(String[] args) {
Wine a = new JNC();
a.fun1();
}
}
-------------------------------------------------
Output:
Wine 的Fun.....
JNC 的Fun2...
分析:在这个程序中子类JNC重载了父类Wine的方法fun1(),重写fun2(),而且重载后的fun1(String a)与 fun1()不是同一个方法,由于父类中没有该方法,向上转型后会丢失该方法,所以执行JNC的Wine类型引用是不能引用fun1(String a)方法。而子类JNC重写了fun2() ,那么指向JNC的Wine引用会调用JNC中fun2()方法。
所以对于多态我们可以总结如下:
多态的必要条件有3个,继承,重写,向上转型。指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用该些方法的时候,必定是使用子类中定义的这些方法(动态连接、动态调用)。
再看一个经典例子
public class A {
public String show(D obj) {
return ("A and D");
}
public String show(A obj) {
return ("A and A");
}
}
public class B extends A{
public String show(B obj){
return ("B and B");
}
public String show(A obj){
return ("B and A");
}
}
public class C extends B{
}
public class D extends B{
}
public class Test {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println("1--" + a1.show(b));
System.out.println("2--" + a1.show(c));
System.out.println("3--" + a1.show(d));
System.out.println("4--" + a2.show(b));
System.out.println("5--" + a2.show(c));
System.out.println("6--" + a2.show(d));
System.out.println("7--" + b.show(b));
System.out.println("8--" + b.show(c));
System.out.println("9--" + b.show(d));
}
}
结果为
1--A and A
2--A and A
3--A and D
4--B and A
5--B and A
6--A and D
7--B and B
8--B and B
9--A and D
分析一下:
1. a1.show(b),由于B extends A,所以真正调用的是A.show(A)
2.a1.show(c),由于C extends B,B extends A,所以真正调用的是A.show(A)
3.a1.show(d),直接调用A.show(D)
4.a2.show(b),可能这里会疑惑为什么不是B and B,原因在于虽然B继承了A,但是父类A中其实是没有show(B)这个方法的,上面有说到, 指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,所以最后是调用的B类的show(A)方法,因为B继承A。
5.a2.show(c),同4理。
6.a2.show(d),父类中存在show(D)方法,而子类B并没有重写,所以直接调用A.show(D)
7. b.show(b),b是创建的B对象,也是B的引用,不存在向上转型,所以直接调用B.show(B)
8.b.show(c),父类A中没有show(C)方法,所以会调用子类,又由于C继承于B,B继承于A,在调用中,会调用继承关系最近的那一个,所以直接调用B.show(B)
9.b.show(d),父类A中有方法show(D),而子类B中并没有重写,所以直接调用A.show(D)
通过上面例子可以发现一个事实,在继承链中对象方法的调用存在一个优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。在例子的5中,a2.show(c),a2是A类型的引用变量,所以this就代表了A,a2.show(c),它在A类中找发现没有找到,于是到A的超类中找(super),由于A没有超类(Object除外),所以跳到第三级,也就是this.show((super)O),C的超类有B、A,所以(super)O为B、A,this同样是A,这里在A中找到了show(A obj),同时由于a2是B类的一个引用且B类重写了show(A obj),因此最终会调用子类B类的show(A obj)方法,结果也就是B and A。