浅谈Java多态

什么是Java中的多态?又是一个纸老虎的概念,老套路,把它具体化,细分化,先想三个问题(注意,这里不是简单的化整为零,而是要建立在学习一个新概念时的思考框架):

1.这个东西有什么用?用来干什么的?它的意义在哪里?(显然,如果是没用的东西,就没必要浪费时间了;其实,弄懂了这个问题,就掌握了50%)

2.这个概念或者技能点怎么用?也就是它的表现形式,如关键字、修饰词、语法什么的。。。(这个占25%)

3.这个东西在用的过程中,有哪些关键点和细节点?(是的,也占25%)

上面三个问题搞清楚了,剩下的就是去用了。。。“无他,但手熟尔。”

 

一、第一个问题:多态有什么用?它存在的意义是什么?

多态的作用:使代码具有松耦合性,满足开闭原则。(多态,意味着一个对象有着多重特征,可以在特定的情况下,表现不同的状态,从而对应着不同的属性和方法。)WTF?又装x!松耦合是什么鬼?还开闭原则?能不能直给?好吧,所谓的“耦合”就是,当你扩展功能的时候,其他与之相应的源代码也要修改,有点像麻花一样纠缠不清;“松耦合”是指你虽然扩展了,但不需要修改其他的代码。这样就达到了对扩展开放,对修改关闭的效果。

具体怎样,看个例子就懂了:小强,一个农村热血青年,来到大城市打工,每天披星戴月,辛勤工作,终于存了些钱。于是小强带着钱回到老家,请村东头的媒婆王大娘给自己介绍对象。小强在焦急等待几天之后,看到王大娘笑眯眯走过来,“强啊,大娘给你找到姑娘了”。。。

故事先讲到这儿,我们用代码来把这件事实现一下:

 1 public class Girl{                //父类--姑娘
 2     public int faceScore=60;    //颜值
 3     public int love=0;
 4     public void say(){        //自我介绍
 5         System.out.println("hello world!");
 6     }
 7     public void addLove(){        //被求爱后,增加爱心值
 8     }
 9 }
10 public class HubeiGirl extends Girl{        //子类--湖北姑娘
11     public int faceScore=70;
12     public void say(){
13         System.out.println("我叫小红,我很聪明,也很会做饭。我的爱心值:"+love);
14     }
15     public void addLove() {                        //重写增加爱心值方法
16         love+=20;
17         System.out.println("我的爱心值是:"+love);
18     }
19     public void cook(){                        //特有方法--做饭
20         System.out.println("红丸子,炸丸子,四喜丸子。。。");
21     }
22 }
23 public class HunanGirl extends Girl{        //子类--湖南姑娘
24     public int faceScore=85;
25     public void say(){
26         System.out.println("我叫小倩,我很可爱,也很会唱歌。我的爱心值:"+love);
27     }
28     public void addLove() {                        //重写增加爱心值方法
29         love+=10;
30         System.out.println("我的爱心值是:"+love);
31     }
32     public void sing(){                        //特有方法--唱歌
33         System.out.println("辣妹子辣~辣妹子辣~~");
34     }
35 }

代码很简单,一个姑娘父类,两个子类分别是湖北姑娘和湖南姑娘,好,现在小强见了两个姑娘想向她们分别表达爱意(被求爱后,爱心值会增加),该怎么实现?代码如下:

 1 public class XiaoQiang{                            //小强类
 2     public void courting(HubeiGirl b){    //向湖北姑娘表达爱意
 3         b.addLove();
 4     }
 5     public void courting(HunanGirl n){    //向湖南姑娘表达爱意
 6         n.addLove();
 7     }
 8 }
 9 public class Test{
10     public static void main(String[] argrs){
11         HubeiGirl xiaohong = new HubeiGirl();
12         xiaohong.say();
13         HunanGirl xiaoqian = new HunanGirl();
14         xiaoqian.say();
15         XiaoQiang qiang = new XiaoQiang(); 
16         qiang.courting(xiaohong);        //小强向湖北姑娘小红表达爱意
17         qiang.courting(xiaoqian);        //小强向湖南姑娘小倩表达爱意    
18     } 
19 }

这里先定义了一个小强类,里面有两个表达爱意的方法,参数是不同的对象类型,属于重载。测试类中创建小红、小倩和小强对象,运行结果如下:

由结果可以看到,传入不同的对象类型参数,小强调用不同的表达爱意的方法courting(),要求满足,bingo!故事继续,王大娘的婚介事业已经走出中国,在向国际化发展,王大娘认识一个非洲姑娘玛丽卡(如上图),要把玛丽卡介绍给小强。。。这要怎么实现?如果照着上面来:

 1 public class AfricaGirl extends Girl{        //子类--非洲姑娘
 2     public int faceScore=80;
 3     public void say(){
 4         System.out.println("我叫玛丽卡,我很热情,也很会跳舞。我的爱心值:"+love);
 5     }
 6     public void addLove() {                        //重写增加爱心值方法
 7         love+=15;
 8         System.out.println("我的爱心值是:"+love);
 9     }
10     public void dance(){                        //特有方法--跳舞
11         System.out.println("动起来!gogogogo for it!动起来!");
12     }
13 }
14 public class XiaoQiang{                            //小强类
15     public void courting(HubeiGirl b){    //向湖北姑娘表达爱意
16         b.addLove();
17     }
18     public void courting(HunanGirl n){    //向湖南姑娘表达爱意
19         n.addLove();
20     }
21     public void courting(AfricaGirl a){    //向非洲姑娘表达爱意
22         a.addLove();
23     }
24 }

这种做法是先定义子类非洲姑娘,这是必须的,在小强类里加了一个courting(AfricaGirl a)方法,这样也能实现要求,但是,这样有两个问题:1.增加非洲姑娘类的时候,改动了小强类,不是松耦合,不满足开闭原则;2.假如王大娘的业务发展的很好,要给小强介绍100个姑娘,难道要改变100次小强类,往里面加100个方法吗?

所以,问题来了:要怎样定义小强类,使得即使不断增加姑娘也不用改动小强类?这里就要用到多态了,也是开篇第二个问题的答案。

 

二、第二个问题:多态怎么用?

其实,就一句话:父类类型的引用指向子类的对象。用多态的思想来定义上面的小强类,如下:

1 public class XiaoQiang{                            //小强类
2     public void courting(Girl g){                //参数为父类类型
3         g.addLove();
4     }
5 }

很神奇,可以看到用多态的思想来做,只需要一个参数为父类Girl类型的方法就够了,先来测试一下:

 1 public class Test {
 2     public static void main(String[] args) {
 3         HubeiGirl xiaohong = new HubeiGirl();
 4         xiaohong.say();
 5         HunanGirl xiaoqian = new HunanGirl();
 6         xiaoqian.say();
 7         AfricaGirl malika = new AfricaGirl();
 8         malika.say();
 9         XiaoQiang qiang = new XiaoQiang(); 
10         qiang.courting(xiaohong);        //小强向湖北姑娘小红表达爱意
11         qiang.courting(xiaoqian);        //小强向湖南姑娘小倩表达爱意
12         qiang.courting(malika);            //小强向非洲姑娘玛丽卡表达爱意
13 
14     }
15 }

功能实现,而且现在无论王大娘给小强介绍多少姑娘都不用修改小强类了。那么,为什么把参数从子类类型变成父类类型就能达到如此效果,就是多态呢?

这里我们先来仔细看一下使用父类作为方法形参实现多态的过程,以“小强向非洲姑娘玛丽卡表达爱意”为例,qiang.courting(malika)执行的时候,由于对象作为参数时,传入的只是对象的引用,因此参数部分发生的是:Girl g = malika,也就是将父类类型Girl的引用g指向了子类对象malika(有的说法是,将子类类型的指针赋值给父类类型的指针,一个意思,一般指针是C和C++里的概念),是的,这就是Java机制允许的父类类型的引用指向子类的对象,就是多态。调用过程是:当我们用一个父类型引用指向子类对象时,会先访问子类中重写的父类方法(父类的方法不会再执行),如果子类没有重写父类的方法,才会执行父类中的方法。而这种调用方式,就是多态的一种状态,叫做向上转型,也是最为容易理解的一种多态方式。具体到上面的例子是,先访问子类AfricaGirl中的重写的addLove()方法,有,就不会再去访问父类Girl中的addLove()方法了。

好,故事继续,老人家讲过:凡是不以结婚为目的的谈恋爱都是耍流氓。小强是正经人,于是他要选一个最中意的姑娘,并把这个姑娘的对象返回出来,好让王大娘进行其它操作。。。这个需求怎么实现?

 1 public class XiaoQiang{                            //小强类
 2     public void courting(Girl g){                //参数为父类类型
 3         g.addLove();
 4     }
 5 
 6     public Girl chooseGirl(int num) {        //选择姑娘的方法,返回值为Girl类型
 7         switch(num) {
 8         case 1:
 9             HubeiGirl b = new HubeiGirl();    //num为1时,返回湖北姑娘的对象b
10             return b;
11         case 2:
12             HunanGirl n = new HunanGirl();
13             return n;
14         case 3:
15             AfricaGirl a = new AfricaGirl();
16             return a;
17             default:
18                 break;
19         }
20         return null;
21     }
22 }

可以看到,小强类中增加了一个选择姑娘的方法,根据不同的数字返回不同的子类姑娘对象,重点是它的返回值是父类Girl类型,这就是使用父类作为返回值实现多态。与上面使用父类作为方法形参实现多态相对,这里是在返回值的时候将父类类型的引用指向子类对象。在测试类Test中调用一下:

1 public class Test {
2     public static void main(String[] args) {
3         XiaoQiang qiang = new XiaoQiang(); 
4         Girl g = qiang.chooseGirl(1);        //返回值是Girl类型,将返回值赋给Girl类型的引用g
5         qiang.courting(g);                    //表达爱意,增加爱心值
6         g.say();                            
7     }
8 }

可以看到,传入数字1,返回的是湖北姑娘小红,既然小红很会做饭,那我们就调用一下她做饭的方法cook(),如下:

看样子不太妙,报错说cook()方法没有定义,有人可能会问子类HubeiGirl中不是有cook()方法吗,问题是现在的g是父类Girl类型的引用,而父类Girl中是没有cook()方法的。这就涉及到向上转型的一个特点:向上转型后,父类类型的引用只能调用子类中的重写的父类方法和父类中的方法,而不能调用子类中特有的方法。这里cook()是子类HubeiGirl特有的方法,所以不能调用。

娶个老婆居然不会做饭,小强肯定不高兴,那该怎么办?这里就需要将返回的父类型返回值强转为子类型,并将其赋值给相应子类型的引用。如下:

可以看到,调用cook()成功!如图所示,将父类Girl类型的返回值强转成子类HubeiGirl类型,再将其赋给子类HubeiGirl类型的引用g,就可以调用子类HubeiGirl中特有的方法了。这种将子类型对象向上转型成父类型后,再将其转回子类型的过程就是所谓的向下转型。(注:不能直接将父类型对象转型成子类型,一定要之前有向上转型这个过程才行。)

好,故事讲完,小强和小红从此过上了幸福的生活。

 

三、第三个问题:使用多态的关键点和细节?

这个问题在二中已经回答得差不多了,总结一下:

1.继承:多态是发生在有继承关系的子类和父类中的。

2.重写:多态就是多种形态。也就是说,当我们需要实现多态的时候,就需要有父类的方法被子类重写。否则,如果没有重写的方法,就看不出多态的特性,一切按照父类的方法来,还不如不要继承,直接在父类中添加相应的方法,然后在实例化好了。

3.向上转型:父类类型的引用指向子类的对象。

4.向下转型:将子类型对象向上转型成父类型后,再将其转回子类型的过程。

 5.在Java中有两种形式可以实现多态,继承和接口。原理相同,本文只讲了继承情况。

 

四、经典例题

既然学了东西就得用,不然那学东西有什么用。下面是一道关于多态的经典例题,可以试一下不看答案能不能做出来:

 1 public class A {
 2     public String show(D obj) {
 3         return ("A and D");
 4     }
 5     public String show(A obj) {
 6         return ("A and A");
 7     } 
 8 }
 9 
10 public class B extends A{
11     public String show(B obj){
12         return ("B and B");
13     }   
14     public String show(A obj){
15         return ("B and A");
16     } 
17 }
18 
19 public class C extends B{
20 
21 }
22 public class D extends B{
23 
24 }
25 
26 public class Test {
27     public static void main(String[] args) {
28         A a1 = new A();
29         A a2 = new B();
30         B b = new B();
31         C c = new C();
32         D d = new D();
33         
34         System.out.println("1--" + a1.show(b));
35         System.out.println("2--" + a1.show(c));
36         System.out.println("3--" + a1.show(d));
37         System.out.println("4--" + a2.show(b));
38         System.out.println("5--" + a2.show(c));
39         System.out.println("6--" + a2.show(d));
40         System.out.println("7--" + b.show(b));
41         System.out.println("8--" + b.show(c));
42         System.out.println("9--" + b.show(d));      
43     }
44 }

运行结果如下:

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

不知道大家做对了几个,有没有像福尔摩斯推理探案一样的感觉,现在就让我们戴上猎鹿帽,叼上烟斗,拿着放大镜开始吧,首先看看下面这张表示ABCD四个类关系的图片:

由上图可以看到:

1.B类中的show(A obj)是继承并重写了A类中的show(A obj)方法;

2.B类中的show(B obj)是重载,B相对A特有的方法;

3.B继承了A的方法,C和D继承了B的方法(包含了A的方法)。

现在来一个一个看:

1---- a1.show(b)

子类对象b作为参数,而a1只能调用show(D obj)和show(A obj)两个方法,显然调用show(A obj),典型的多态应用。

2----a1.show(c)

子类的子类对象c作为参数,同样的,a1只能show(D obj)和show(A obj)两个方法,显然调用show(A obj),这里表现的是多态中,子类对象不光可以赋给父类的引用,父类以上的引用都可以。而Java里最终极的父类是Object,所以,Object的引用可以指向任何对象。

3----a1.show(d)

子类的子类对象d作为参数,同样的,a1只能show(D obj)和show(A obj)两个方法,显然。。。额,里面有D类型作为参数的方法,所以显然调用show(D obj)。

4----a2.show(b)

A a2 = new B(); 其中,a2是父类型的引用指向了子类B类型的对象,多态的应用,所以a2能调用的方法有:A类中的show(D obj)、B类中的show(A obj)两个方法。为什么不能调用A类中的show(A obj)方法?因为子类B已经继承并重写了父类A类中的show(A obj), 所以只会访问子类B中的show(A obj),不会再去访问父类A中的show(A obj)。为什么不能调用B类中的show(B obj)方法?因为此方法是子类B特有的方法,多态中父类A类型的引用a2不能访问。现在是a2.show(b),参数是子类对象b,显然调用B类中的show(A obj)方法。

5----a2.show(c)

同上,a2能调用的方法有:A类中的show(D obj)、B类中的show(A obj)两个方法。B的子类对象c作为实参,显然调用B类中的show(A obj)方法,类C的父类的父类型作为形参实现多态。

6---a2.show(d)

同上,a2能调用的方法有:A类中的show(D obj)、B类中的show(A obj)两个方法。B的子类对象d作为实参,里面有D类型作为形参的方法,所以显然调用A类中的show(D obj)方法。

7----b.show(b)

B b = new B();这个就是调用B类中的show(B obj)。

8----b.show(c)-------------------它仍然要按照继承链中调用方法的优先级来确认。

B b = new B();其中,B类继承了A类的方法,b可以调用的方法有:A类中的show(D obj)、B类中的show(A obj)、B类中的show(B obj)三个方法。B的子类对象c作为实参,调用父类B中的show(B obj),使用父类作为形参实现多态。为什么不能调用B类中的show(A obj)方法?类A是类C父类的父类,将A类型的引用指向类C的对象c不也是多态允许的?理论上是的,但实际情况是,现在有父类作为形参和父类的父类作为形参实现多态这两种选择。这时它就要按照按照继承链中调用方法的优先级来确认,父类B比父类的父类A优先。其中优先级为:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。

9----b.show(d)

同上,b可以调用的方法有:A类中的show(D obj)、B类中的show(A obj)、B类中的show(B obj)三个方法。。。额,里面有D类型作为参数的方法,所以显然调用A类中的show(D obj)。

 

posted @ 2017-09-20 23:58  可比克番茄  阅读(506)  评论(2编辑  收藏  举报