什么是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. 201521123034 《Java程序设计》第3周学习总结

    1. 本章学习总结 看不清点这个:http://naotu.baidu.com/file/c01303326572f7916e506ec5f55270a4 2. 书面作业 1.代码阅读 public ...

  2. Java实现Windows平台下Ping的最佳方法

    先上结论:通过调用系统自带的Ping命令来实现,使用exitValue()值来判断Ping的结果.按照惯例,0表示ok,1表示不通. private static void pingTest1() t ...

  3. php memcache 扩展 php -m 与 phpinfo() 不同

    事情起因,因要升级 openssl(openssl升级这里不表) ,所以在升级后对 php 也进行了从新编译,编译成功. 发现没有安装,memcache 扩展,从新编译安装了一下,显示的安装成功,但是 ...

  4. 建一座安全的“天空城” ——揭秘腾讯WeTest如何与祖龙共同挖掘手游安全漏洞

    作者:腾讯WeTest手游安全测试团队商业转载请联系腾讯WeTest获得授权,非商业转载请注明出处. WeTest导读 <九州天空城3D>上线至今,长期稳定在APP Store畅销排行的前 ...

  5. C# 各种常用集合类型的线程安全版本

    在C#里面我们常用各种集合,数组,List,Dictionary,Stack等,然而这些集合都是非线程安全的,当多线程同时读写这些集合的时候,有可能造成里面的数据混乱,为此微软从Net4.0开始专门提 ...

  6. CSS3微信启动页天王星版

    今天被微信启动页刷屏了. 一直还以为启动页背景显示的月球的.今天才了解到这么有含义. 我也蹭一下微信的热度,做一个HTML+CSS版本的. 用CSS画地球太困难了,来个简单点的,天王星版. 主要使用到 ...

  7. AngularJS 1.3中的一次性数据绑定(one-time bindings)

    点击查看AngularJS系列目录 谈谈AngularJS 1.3中的一次性数据绑定(one-time bindings) 不久之前,AngularJS 1.3版本正式发布,其中添加了很多的性特性,同 ...

  8. WebSocket 开发模拟客户端与有游戏服务器通信

    WebSocket 客户端测试功能 websocket是有标准的通信协议,在h2engine服务器引擎中继承了websocket通信协议,使用websocket通信协议的好处是很多语言或框架都内置了w ...

  9. jquery.i18n.properties前端国际化解决方案“填坑日记”

    但现在的情况是老的项目并没有使用这类架构.说起国际化,博主几年前就做过,在MVC里面实现国际化有通用的解决方案,主要就是通过资源文件的方式定义多语言.最初接到这个任务,并没有太多顾虑,毕竟这种东西有很 ...

  10. 关于select的一个错误---属性选择器

    错误: jquery 获取下拉框 text='1'的 option 的value 属性值  我写的var t= $("#selectID option[text='1']).val() ; ...