1. 什么是多态
编程中多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
比如你是一个酒神,对酒情有独钟。某日回家发现桌上有几个杯子里面都装了白酒,从外面看我们是不可能知道这是些什么酒,只有喝了之后才能够猜出来是何种酒。你一喝,这是剑南春、再喝这是五粮液、再喝这是酒鬼酒….在这里我们可以描述成如下:
酒 a = 剑南春
酒 b = 五粮液
酒 c = 酒鬼酒
…
这里所表现的的就是多态。剑南春、五粮液、酒鬼酒都是酒的子类,我们只是通过酒这一个父类就能够引用不同的子类,这就是多态——我们只有在运行的时候才会知道引用变量所指向的具体实例对象。
诚然,要理解多态我们就必须要明白什么是“向上转型”。这里再讲解一下:在上面的喝酒例子中,酒(Win)是父类,剑南春(JNC)、五粮液(WLY)、酒鬼酒(JGJ)是子类。我们定义如下代码:
NC a = new JNC();
对于这个代码我们非常容易理解无非就是实例化了一个剑南春的对象嘛!但是这样呢?
Wine a = new JNC();
在这里我们这样理解,这里定义了一个Wine 类型的a,它指向JNC对象实例。由于JNC是继承与Wine,所以JNC可以自动向上转型为Wine,所以a是可以指向JNC实例对象的。这样做存在一个非常大的好处,在继承中我们知道子类是父类的扩展,它可以提供比父类更加强大的功能,如果我们定义了一个指向子类的父类引用类型,那么它除了能够引用父类的共性外,还可以使用子类强大的功能。
2. 多态的定义和使用格式
父类的引用变量指向子类对象:
父类类型 变量名 = new 子类类型();
- 普通类多态定义格式:父类 变量名 = new 子类();
class Fu {} //创建父类
class Zi extends Fu {} //创建子类
Fu f = new new Zi(); //类的多态使用
- 抽象类多态定义格式:抽象名 变量名 = new 抽象子类();
//创建抽象父类
abstract class Fu
{
public abstract void fun();
}
//创建子类重写父类抽象方法
class Zi extends Fu
{
public void fun()
{
System.out.println("重写父类抽象方法");
}
}
//抽象类的多态使用
Fu f = new Zi();
- 接口多态定义格式::接口 变量名 = new 接口实现类();
//创建接口
interface Fu
{
public abstract void fun();
}
//创建实现类
class Zi implements Fu
{
public void fun()
{
System.out.println("重写接口抽象方法");
}
}
//接口的多态使用
Fu f = new Zi();
3. 多态的特点
- 必须有父子类关系或类实现接口关系,否则无法完成多态
- 必须重写父类或接口类方法
- 父类引用变量指向子类对象
- 父类引用变量调用方法时,会调用子类重写后的方法
4.编译和运行时以子类为准还是以父类为准
- 成员变量
编译:成员变量在编译时,参考的是引用类型所属的类(父类)中是否有被调用的成员变量。没有则编译失败。
运行:成员变量在运行时,参考的也是引用类型所属的类(父类)中是否有被调用的成员变量。没有则运行失败。
即:编译和运行都是看等号的左边(父类)
- 成员方法
编译:成员方法编译时,参考引用变量所属的类(父类),如果类中没有调用的方法,编译失败
运行:成员方法运行时,参考引用变量所指的对象所属的类(子类),并运行对象所属类中的方法
举个栗子:
//创建父类
public class Fu {
int num = 1; //定义成员变量
public void fun() //定义成员方法
{
System.out.println("父类");
}
}
//创建子类
public class Zi extends Fu {
int num = 2;
public void fun()
{
System.out.println("子类");
}
}
//main中调用
public class Main {
public static void main(String[] args)
{
Fu f = new Zi();
System.out.println(f.num); //成员变量编译和运行都是参考父类的
f.fun(); //成员方法编译参考父类,运行参考子类
}
}
运行结果:
1
子类
5.instance of 关键字
作用:instanceof 关键字用来判断某个对象是否属于某种数据类型
举个栗子:
//两个子类,使用两次多态调用
Animal a1 = new Cat();
Animal a2 = new Dog();
boolean flag = a1 instanceof Cat; //flag结果为true
boolean flag2 = a2 instanceof Cat; //flag2结果为false
6.多态转型
向上转型
- 当子类对象赋值给一个父类引用时,即向上转型(多态本身就是向上转型的过程)
- 格式:父类类型 变量名 = new 子类类型();
eg:Fu f = new Zi();
向下转型
- 通过强制类型转换格式,将父类引用转为子类格式
- 格式:子类类型 变量名 = (子类类型)父类类型的变量;
eg:Zi z = (Zi) f; //变量 f 实际上是指向 Zi 对象
7.什么时候使用多态
理论上来讲:
- 存在一个继承体系结构。如果不存在继承体系结构,那么所谓的多态就无从谈起
- 在该继承体系结构中的所有类里面,均需要实现一个统一的函数接口,且该函数接口的行为在运行时才能确定这样的情形,就可以考虑使用多态
为了方便理解,再讲一个应用场景:
有一个类CShape,里面有个DrawMyself(),用来在屏幕上输出自己的形状,圆的话就画圆,方的话就画方。你从一个文件读入10000个数据,然后显示在屏幕上。程序怎么设计:
A.
所有的图形都是CShape,所以我在DrawMyself()区别,根据图形的类型我来画不同的形状,于是在这里面有成对的Switch Case对。如果每一天我们增加了一种类型,咱么办,必须修改CShape中的DrawMyself()函数,所有的代码都在这里。当心,代码很多。
B。所有的图形都是CShape,但是不同的图形DrawMyself()输出不同。好,我使用派生类,于是CRectShape、CTriangleShpe、CCircleShape,CElipseShape就产生了,每个派生类都有自己的DrawMyself()函数,但这个函数功能是不一样的。
这样的好处显而易见,某天多了一种形状,你怎么办?增加一个派生类啊。你只修改这个派生类的代码,你根本不用去碰其它已经存在的、运行良好的代码。
然后实例化的时候就会有这样的代码:
public class CShapeFactory
{
public static CShape createCShape(string cShapeName)
{
if (cShapeName.Equals("CRectShape"))
{
return new CRectShape();
}
else if (carName.Equals("CTriangleShpe"))
{
return new CTriangleShpe();
}
else
{
Console.WriteLine("什么都不是");
return null;
}
}
}
他会根据不同的形状返回不同形状的对象实例。
当然了,上面只是一个简单的例子来说明多态,多态并不是单单为了修改代码方便。其实很多东西,单看理论高深莫测,其实用的多了,自然就理解了