多态
面向对象的三大特性:封装、继承、多态。在这三个特性中,如果没有封装和继承,也不会有多态。
那么多态实现的途径和必要条件是什么呢?以及多态中的重写和重载在JVM中的表现是怎么样?
1.多态实现的必要条件
(1)子类必须继承父类
对于子类必须继承父类,小编个人认为,是因为按照面向对象的五大基本原则所说的中的依赖倒置原则:抽象不依赖于具体,具体依赖于抽象。既然要实现多态,那么必定有一个作为"抽象"类来定义“行为”,以及若干个作为"具体"类来呈现不同的行为形式或形态。
所以我们给出的一个具体类——白切鸡类:
class BaiqieChicken extends Chicken{ }
但仅是定义一个白切鸡类是不够的,因为在此我们只能做到复用父类的属性和行为,而没有呈现出行为上的不同的形式或形态。
(2)必须有重写
重写,简单地理解就是重新定义的父类方法,使得父类和子类对同一行为的表现形式各不相同。我们用白切鸡类来举个栗子。
class BaiqieChicken extends Chicken{
public void live(){
System.out.println("这是一只会被做成白切鸡的鸡");
}
}
这样就实现了重写,鸡类跟白切鸡类在live()方法中定义的行为不同,鸡类是一只命运有着无限可能的鸡,而白切鸡类的命运就是做成一只白切鸡。
(3)父类引用指向子类对象
其实这个条件是面向对象的五大基本原则里面的里氏替换原则,简单说就是父类可以引用子类,但不能反过来。
当一只鸡被选择做白切鸡的时候,它的命运就不是它能掌控的。
Chicken c = new BaiqieChicken();
c.live();
什么要有这个原则?因为父类对于子类来说,是属于“抽象”的层面,子类是“具体”的层面。“抽象”可以提供接口给“具体”实现,但是“具体”凭什么来引用“抽象”呢?而且“子类引用指向父类对象”是不符合“依赖倒置原则”的。
当一只白切鸡想回头重新选择自己的命运,抱歉,它已经在锅里,逃不出去了。
2.多态的实现途径
多态的实现途径有三种:重写、重载、接口实现,虽然它们的实现方式不一样,但是核心都是:同一行为的不同表现形式。
(1) 重写
重写,指的是子类对父类方法的重新定义,但是子类方法的参数列表和返回值类型,必须与父类方法一致!所以可以简单的理解,重写就是子类对父类方法的核心进行重新定义。
class Chicken{
public void live(String lastword){
System.out.println(lastword);
}
}
class BaiqieChicken extends Chicken{
public void live(String lastword){
System.out.println("这只白切鸡说:");
System.out.println(lastword);
}
}
(2) 重载
重载,指的是在一个类中有若干个方法名相同,但参数列表不同的情况,返回值可以相同也可以不同的方法定义场景。也可以简单理解成,同一行为(方法)的不同表现形式。
class Chicken{
public void live(String lastword){
System.out.println(lastword);
}
}
class BaiqieChicken extends Chicken{
public void live(String lastword){
System.out.println("这只白切鸡说:");
System.out.println(lastword);
}
}
(3)接口实现
接口,是一种无法被实例化,但可以被实现的抽象类型,是抽象方法的集合,多用作定义方法集合,而方法的具体实现则交给继承接口的具体类来定义。所以,接口定义方法,方法的实现在继承接口的具体类中定义,也是对同一行为的不同表现形式。
interface Chicken{
public void live();
}
class BaiqieChicken implements Chicken{
public void live(){
System.out.println("这是一只会被做成白切鸡的鸡");
}
}
class ShousiChicken implements Chicken{
public void live(){
System.out.println("这是一只会被做成手撕鸡的鸡");
}
}
3.运行时多态和编译时多态
编译时的多态会发生在方法重载的时候,方法的重载指方法名相同,方法的参数列表不同,这样在类加载的
时候就会加载两个方法的版本。这个过程是在编译期就是确定的,这时候我们通过不同参数调用相同函数名的
时候就会有不同的结果,这是编译时的多态,需要注意的加载函数版本不同是根据参数列表确定的,而不是根据方
法返回值类型确定的,也就是说方法参数列表不同才会有重载。调用不同版本函数的时候才会有不同的结果
运行时的多态发生在继承的时候,父类有个方法,子类重写(覆盖)了父类的方法。父类有一个函数,子类定义了一个
和父类相同的函数,包括参数列表相同。这时候由父类对象指向子类的实例的时候,就会调用子类的方法。这是运行
时多态的典型例子。下面我们也会给出具体的代码来让我们更深入的了解继承之后的运行时多态。