1. 多态的概念
多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
很多初学者往往在理解多态上会比较吃力,甚至误解成重载,多态不是方法的重载,不是方法名一样方法的参数不一样,不是一个参数有多种形式就称之为多态,那是不正确的,如果这就是多态的话那么何必有方法的重载?直接改名多态就行了。父类 a = 子类对象 就是子类对象可以披上父类的衣服,只要穿上了父类的衣服就装扮成了父类 可以做父类的一些事情灵活性强。多态最重要的目的就是为了让子类转换成父类。
举个例子:
2. 多态存在的三个必要条件
- 继承
- 重写
- 父类的引用指向子类的对象:Parent p = new Child();
3. 案例分析
下面通过一个例子,让我们更好地理解多态这个概念。
假如你是一个女人,对化妆品情有独钟。某日去商场发现有几个店铺里面都有你想要的一款口红,从外面看我们是不可能知道这些点分别卖的价格是多少,只有进去问了之后才能得到口红的价格。于是你开始逐一进店,第一个店卖100、第二个店卖150、第三个店卖200….在这里我们可以描述成如下:
店1 = 100 --> Store1 = 100;
店2 = 150 --> Store2 = 150;
店3 = 200 --> Store3 = 200;
…
这里所表现的的就是多态。店铺1、店铺2、店铺3都是商场的子类,我们只是通过商场这一个父类就能够引用不同的子类,这就是多态——我们只有在运行的时候才会知道引用变量所指向的具体实例对象,也就是我们只有实际购买了才知道我们选的哪家店。
下面还需要大家理解一下“向上转型",要理解多态我们就必须要明白什么是“向上转型”。什么是“向上转型”呢?它是JAVA中的一种调用方式。假如存在 B继承A,向上转型是对A的对象的方法的扩充,即A的对象可访问B从A中继承来的和B“重写”A的方法。
在上面的商铺例子中,商场(Market)是父类,店铺1(Store1)、店铺2(Store2)、店铺3(Store3)是子类。我们定义如下代码:
Store1 store1 = new Store1();
对于这个代码我们非常容易理解无非就是实例化了一个商铺1的对象嘛!但是这样呢?
Market market = new Store1();
在这里我们这样理解,这里定义了一个商场类型的market,它指向商铺1对象实例。由于商铺1是继承的商场,所以商铺1可以自动向上转型为商场,所以market是可以指向商铺1实例对象的。这样做存在一个非常大的好处,在继承中我们知道子类是父类的扩展,它可以提供比父类更加强大的功能,如果我们定义了一个指向子类的父类引用类型,那么它除了能够引用父类的共性外,还可以使用子类强大的功能。
但是向上转型存在一些缺憾,那就是它必定会导致一些方法和属性的丢失,而导致我们不能够获取它们。所以父类类型的引用可以调用父类中定义的所有属性和方法,对于只存在与子类中的方法和属性它就望尘莫及了。
下面我们用代码来实现一下上面的例子:
package com.wedu;
//商场类
class Market {
public void fun1(){
System.out.println("Market 的Fun1.....");
fun2();
}
public void fun2(){
System.out.println("Market 的Fun2...");
}
}
//商铺1继承商场
class Store1 extends Market{
/**
* @desc 子类重载父类方法
* 父类中不存在该方法,向上转型后,父类是不能引用该方法的
* @param a
* @return void
*/
public void fun1(String a){
System.out.println("Store1 的 Fun1...");
fun2();
}
/**
* 子类重写父类方法
* 指向子类的父类引用调用fun2时,必定是调用该方法
*/
public void fun2(){
System.out.println("Store1 的Fun2...");
}
}
//测试类
class Test1 {
public static void main(String[] args) {
Market market = new Store1();
market.fun1();
}
}
Market 的Fun.....
Store1 的Fun2...
从程序的运行结果中我们发现,market.fun1()首先是运行父类Market中的fun1().然后再运行子类Store1中的fun2()。
分析:在这个程序中子类Store1重载了父类Market的方法fun1(),重写fun2(),而且重载后的fun1(String a)与 fun1()不是同一个方法,由于父类中没有该方法,向上转型后会丢失该方法,所以执行Store1的Market类型引用是不能引用fun1(String a)方法的。而子类Store1重写了fun2() ,那么指向Store1的Market引用会调用Store1中fun2()方法。
所以对于多态我们可以总结如下:
指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用该些方法的时候,必定是使用子类中定义的这些方法(动态连接、动态调用)。
对于面向对象而已,多态分为编译时多态和运行时多态。其中编译时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编译之后会变成两个不同的函数,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。
4. 多态的实现方式
-
方式一:重写
-
方式二:接口(学习接口之后会详细解释)
- 生活中的接口最具代表性的就是插座,例如一个三接头的插头都能接在三孔插座中,因为这个是每个国家都有各自规定的接口规则,有可能到国外就不行,那是因为国外自己定义的接口类型。
- java中的接口类似于生活中的接口,就是一些方法特征的集合,但没有方法的实现。
-
方式三:抽象类和抽象方法(学习抽象之后会详细解释)