(1)多态概述
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
(2)多态详述
比如你是一个酒神,对酒情有独钟。某日回家发现桌上有几个杯子里面都装了白酒,从外面看我们是不可能知道这是些什么酒,只有喝了之后才能够猜出来是何种酒。你一喝,这是剑南春、再喝这是五粮液、再喝这是酒鬼酒….在这里我们可以描述成如下:
酒 a = 剑南春
酒 b = 五粮液
酒 c = 酒鬼酒
…
这里所表现的的就是多态。剑南春、五粮液、酒鬼酒都是酒的子类,我们只是通过酒这一个父类就能够引用不同的子类,这就是多态——我们只有在运行的时候才会知道引用变量所指向的具体实例对象。多态在这个例子中也可以解释为,酒在不同的引用中所表现出来的不同形态或者状态。酒既可以是五粮液,也可以是酒鬼酒。酒既有了某种形态的特性,也保留了所属于“酒”这一个大类的基本属性。
诚然,要理解多态我们就必须要明白什么是“向上转型”。在继承中我们简单介绍了向上转型,这里就在啰嗦下:在上面的喝酒例子中,酒(Win)是父类,剑南春(JNC)、五粮液(WLY)、酒鬼酒(JGJ)是子类。我们定义如下代码:
JNC a = new JNC();
对于这个代码我们非常容易理解无非就是实例化了一个剑南春的对象嘛!但是这样呢?
Wine a = new JNC();//父类类型指向子类实例对象
在这里我们这样理解,这里定义了一个Wine 类型的a,它指向JNC对象实例。由于JNC是继承与Wine,所以JNC可以自动向上转型为Wine,所以a是可以指向JNC实例对象的。这样做存在一个非常大的好处,在继承中我们知道子类是父类的扩展,它可以提供比父类更加强大的功能,如果我们定义了一个指向子类的父类引用类型,那么它除了能够引用父类的共性外,还可以使用子类强大的功能。
但是向上转型存在一些缺憾,那就是它必定会导致一些方法和属性的丢失,而导致我们不能够获取它们。所以父类类型的引用可以调用父类中定义的所有属性和方法(这些可能在子类中被重新定义过,父类中此时的调用会被覆盖),对于只存在与子类中的方法和属性它就望尘莫及了。
这一点,我们可以通过一小段代码来体会一下。
仔细观察两段代码。在第一段代码中,我们定义了一个Animal类,一个Cat类。我们在Animal类中定义了 函数eat(),在Cat类中对eat()方法进行的重写,并添加了一个在父类中没有定义过的函数nun()。在主函数中分别对与子类实例对象,和指向子类实例对象的父类类型进行两种函数的调用测试。子类对象可以完美的运行在子类中进行重新编写或者初步定义的函数,但指向子类实例对象的父类类型在调用num()函数时出现了错误,这是因为nun()函数在Animal类中没有被定义过。
由此我们可以得到多态应用的前提:
- 要有继承或实现关系
- 要有方法的重写
- 要有父类引用指向子类对象
(3)扩展-使用场景
根据多态不能调用子类单独有的代码的特性,当所要创建的对象在一种基本情况上面要表现不同的特性时,我们可以使用多态来让对象表现不同的状态。具体情况包括:
- 定义方法参数列表时
- 定义方法返回值类型时
- 定义类的成员变量时
- 定义数组元素类型时
- 都定义为父类类型,这样就可以传递、返回、赋值、装任意子类类型的对象定义时,定义父类类型使用时使用子类类型的对象,等等。
在这些情况下,若不使用多态来解决问题,将会导致代码出现以下几种问题:
- 1:代码复用性差:有几种灯泡类型 在台灯类中就需要定义几个属性 而且在on方法中进行多次if判断
- 2:扩展性差:有新灯泡类型 需要更改Lamp类 添加新属性 修改on方法
- 3:耦合性强
例如:
public class LianXi1_1 {
//不使用多态
public static void main(String[] args) {
//创建灯泡对象
RedBulb1 r1=new RedBulb1();
GreenBulb1 g1=new GreenBulb1();
//创建台灯对象
Lamp1 l1=new Lamp1();
//给台灯对象的属性赋值
l1.r=r1;
l1.g=g1;
//调用台灯对象的方法
l1.on();
}
}
class Lamp1{//台灯类
//灯泡是台灯的一个数据
RedBulb1 r;//定义成员变量记录需要安装的红灯泡
GreenBulb1 g;//定义成员变量记录需要安装的绿灯泡
void on() {
if (r!=null) {r.light();}
if (g!=null) {g.light();}
}
}
class RedBulb1{
void light(){
System.out.println("红灯泡 发光:红");
}
}
class GreenBulb1{
void light(){
System.out.println("绿灯泡 发光:绿");
}
}
public class LianXi1_2 {
//使用多态
public static void main(String[] args) {
//创建灯泡对象
RedBulb r1=new RedBulb();
GreenBulb g1=new GreenBulb();
//创建台灯对象
Lamp l1=new Lamp();
//给台灯对象的属性赋值
l1.b=r1;
l1.b=g1;
//调用台灯对象的方法
l1.on();
}
class Lamp{//台灯类
//灯泡是台灯的一个数据
//定义父类类型的成员变量
Buib2 b;
void on() {
if (b!=null) {b.light();}
}
}
//抽取子类中共同数据和功能形成父类
abstract class Buib2{
abstract void light();
}
//让所有子类继承父类
class RedBulb extends Buib2{
//根据子类需求 实现父类方法
void light() {
System.out.println("红灯泡 发光:红");
}
}
class GreenBulb extends Buib2{
void light() {
System.out.println("绿灯泡 发光:绿");
}
}
两相对比下可以看出,在这种情况下使用多态不仅可以让代码有序,还可以有更强的逻辑性。
(4)小结
1.多态的使用前提:
- 要有继承或实现关系
- 要有方法的重写
- 要有父类引用指向子类对象
2.多态的好处和弊端
(1)好处
- 提高程序的扩展性。定义方法的时候,使用父类型作为参数,在使用的时候,使用具体的子类型参与操作
(2)坏处
- 不能访问子类中的特有成员
3.多态中的转型
- 向上转型
父类引用指向子类对象就是向上转型 - 向下转型
格式:子类型 对象名 = (子类型)父类引用;