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

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

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

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

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

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

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

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

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

 public class Girl{                //父类--姑娘
public int faceScore=60; //颜值
public int love=0;
public void say(){ //自我介绍
System.out.println("hello world!");
}
public void addLove(){ //被求爱后,增加爱心值
}
}
public class HubeiGirl extends Girl{ //子类--湖北姑娘
public int faceScore=70;
public void say(){
System.out.println("我叫小红,我很聪明,也很会做饭。我的爱心值:"+love);
}
public void addLove() { //重写增加爱心值方法
love+=20;
System.out.println("我的爱心值是:"+love);
}
public void cook(){ //特有方法--做饭
System.out.println("红丸子,炸丸子,四喜丸子。。。");
}
}
public class HunanGirl extends Girl{ //子类--湖南姑娘
public int faceScore=85;
public void say(){
System.out.println("我叫小倩,我很可爱,也很会唱歌。我的爱心值:"+love);
}
public void addLove() { //重写增加爱心值方法
love+=10;
System.out.println("我的爱心值是:"+love);
}
public void sing(){ //特有方法--唱歌
System.out.println("辣妹子辣~辣妹子辣~~");
}
}

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

 public class XiaoQiang{                            //小强类
public void courting(HubeiGirl b){ //向湖北姑娘表达爱意
b.addLove();
}
public void courting(HunanGirl n){ //向湖南姑娘表达爱意
n.addLove();
}
}
public class Test{
public static void main(String[] argrs){
HubeiGirl xiaohong = new HubeiGirl();
xiaohong.say();
HunanGirl xiaoqian = new HunanGirl();
xiaoqian.say();
XiaoQiang qiang = new XiaoQiang();
qiang.courting(xiaohong); //小强向湖北姑娘小红表达爱意
qiang.courting(xiaoqian); //小强向湖南姑娘小倩表达爱意
}
}

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

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

 public class AfricaGirl extends Girl{        //子类--非洲姑娘
public int faceScore=80;
public void say(){
System.out.println("我叫玛丽卡,我很热情,也很会跳舞。我的爱心值:"+love);
}
public void addLove() { //重写增加爱心值方法
love+=15;
System.out.println("我的爱心值是:"+love);
}
public void dance(){ //特有方法--跳舞
System.out.println("动起来!gogogogo for it!动起来!");
}
}
public class XiaoQiang{ //小强类
public void courting(HubeiGirl b){ //向湖北姑娘表达爱意
b.addLove();
}
public void courting(HunanGirl n){ //向湖南姑娘表达爱意
n.addLove();
}
public void courting(AfricaGirl a){ //向非洲姑娘表达爱意
a.addLove();
}
}

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

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

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

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

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

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

 public class Test {
public static void main(String[] args) {
HubeiGirl xiaohong = new HubeiGirl();
xiaohong.say();
HunanGirl xiaoqian = new HunanGirl();
xiaoqian.say();
AfricaGirl malika = new AfricaGirl();
malika.say();
XiaoQiang qiang = new XiaoQiang();
qiang.courting(xiaohong); //小强向湖北姑娘小红表达爱意
qiang.courting(xiaoqian); //小强向湖南姑娘小倩表达爱意
qiang.courting(malika); //小强向非洲姑娘玛丽卡表达爱意 }
}

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

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

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

 public class XiaoQiang{                            //小强类
public void courting(Girl g){ //参数为父类类型
g.addLove();
} public Girl chooseGirl(int num) { //选择姑娘的方法,返回值为Girl类型
switch(num) {
case 1:
HubeiGirl b = new HubeiGirl(); //num为1时,返回湖北姑娘的对象b
return b;
case 2:
HunanGirl n = new HunanGirl();
return n;
case 3:
AfricaGirl a = new AfricaGirl();
return a;
default:
break;
}
return null;
}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

四、经典例题

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

 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));
}
}

运行结果如下:

--A and A
--A and A
--A and D
--B and A
--B and A
--A and D
--B and B
--B and B
--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)。

浅谈Java多态的更多相关文章

  1. 浅谈Java中set.map.List的区别

    就学习经验,浅谈Java中的Set,List,Map的区别,对JAVA的集合的理解是想对于数组: 数组是大小固定的,并且同一个数组只能存放类型一样的数据(基本类型/引用类型),JAVA集合可以存储和操 ...

  2. Java基础学习总结(29)——浅谈Java中的Set、List、Map的区别

    就学习经验,浅谈Java中的Set,List,Map的区别,对JAVA的集合的理解是想对于数组: 数组是大小固定的,并且同一个数组只能存放类型一样的数据(基本类型/引用类型),JAVA集合可以存储和操 ...

  3. 浅谈Java中接口与抽象类的异同

    浅谈Java中接口与抽象类的异同 抽象类和接口这两个概念困扰了我许久,在我看来,接口与抽象类真的十分相似.期间也曾找过许许多多的资料,参考了各路大神的见解,也只能是简简单单地在语法上懂得两者的区别.硬 ...

  4. 龙生九子-浅谈Java的继承

    龙生九子-浅谈Java的继承 书接上回,我们之前谈过了类和对象的概念,今天我来讲一讲面向对象程序设计的另外一个基本概念-继承 目录 为什么需要继承 自动转型与强制转型 继承能干啥 复写和隐藏 supe ...

  5. 浅谈Java接口(Interface)

    浅谈Java接口 先不谈接口,不妨设想一个问题? 如果你写了个Animal类,有许多类继承了他,包括Hippo(河马), Dog, Wolf, Cat, Tiger这几个类.你把这几个类拿给别人用,但 ...

  6. 浅谈Java的throw与throws

    转载:http://blog.csdn.net/luoweifu/article/details/10721543 我进行了一些加工,不是本人原创但比原博主要更完善~ 浅谈Java异常 以前虽然知道一 ...

  7. 浅谈Java中的equals和==(转)

    浅谈Java中的equals和== 在初学Java时,可能会经常碰到下面的代码: 1 String str1 = new String("hello"); 2 String str ...

  8. 浅谈Java中的对象和引用

    浅谈Java中的对象和对象引用 在Java中,有一组名词经常一起出现,它们就是“对象和对象引用”,很多朋友在初学Java的时候可能经常会混淆这2个概念,觉得它们是一回事,事实上则不然.今天我们就来一起 ...

  9. 浅谈Java中的equals和==

    浅谈Java中的equals和== 在初学Java时,可能会经常碰到下面的代码: String str1 = new String("hello"); String str2 = ...

随机推荐

  1. UICollectionView中Cell左对齐 居中 右对齐 等间距------你想要的,这里都有

    支持靠左,居中,靠右,等间距对齐. 靠左等间距.png 居中等间距.png 靠右等间距.png #import <UIKit/UIKit.h> typedef NS_ENUM(NSInte ...

  2. html加载和解析流程

    之前查找资料了解了html的整个渲染过程,对于理解页面加载帮助还是蛮大的,下面我用visio把它画成流程图,便于直观理解 好吧,居然要150字才能发布............ 浏览器渲染过程 浏览器渲 ...

  3. 团队作业4——第一次项目冲刺(Alpha版本) Day1

    1.开站立式会议: 2.Leangoo任务分解图: 3.开会讨论的结果,任务分派 队员 今日进展 明日安排 林燕 调查产品的市场需求,学习微信开发 完善逻辑架构框架 王李焕 结合实际分析系统设计思路, ...

  4. 201521123105 第三周Java学习总结

    1. 本周学习总结 对象(实际个体) 对象与类 类(模板) 2.书面作业 1.代码阅读 public class Test1 { private int i = 1;//这行不能修改 private ...

  5. 201521123069 《Java程序设计》 第14周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多数据库相关内容. 2. 书面作业 1. MySQL数据库基本操作 建立数据库,将自己的姓名.学号作为一条记录插入.(截图,需出现自 ...

  6. 201521123100《Java程序设计》第14周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多数据库相关内容. 2. 书面作业 1. MySQL数据库基本操作 建立数据库,将自己的姓名.学号作为一条记录插入.(截图,需出现自 ...

  7. 201521123100 《java程序设计》第12周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多流与文件相关内容. 2. 书面作业 将Student对象(属性:int id, String name,int age,doubl ...

  8. 201521123045 《Java程序设计》 第十三周学习总结

    201521123045 <Java程序设计> 第十三周学习总结 1. 本周学习总结 2. 书面作业 Q1.网络基础 1.1 比较ping www.baidu.com与ping cec.j ...

  9. 201521123122 《java程序设计》第十四周学习总结

    ## 201521123122 <java程序设计>第十四周实验总结 ## 1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多数据库相关内容. 2. 书面作业 1. M ...

  10. 201521123115《Java程序设计》第14周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多数据库相关内容. 2. 书面作业 1. MySQL数据库基本操作 建立数据库,将自己的姓名.学号作为一条记录插入.(截图,需出现自 ...