什么叫做多态?
从字面上来讲,多态就是多种形态。
在我第一次接触Java多态的时候,我的讲师曾经在课堂上讲过这样一段话:
多态就是指子类继承父类的属性和方法,并对父类的方法加以改造,使之适用于当前子类应用不同业务场景的一种表现形式。”
定义可能不是那么的直观,下边我们看一个案例:
class Animal{
public void shout() {
System.out.println("动物会叫");
}
}
class Dog extends Animal{
@Override
public void shout() {
System.out.println("狗:汪汪汪");
}
}
class Cat extends Animal{
@Override
public void shout() {
System.out.println("猫:喵喵喵");
}
}
class Human extends Animal{
@Override
public void shout() {
System.out.println("人:鸡你太美~");
}
}
public class Demo1 {
public static void main(String[] args) {
Animal animal = new Animal();
animal.shout();
Human human = new Human();
human.shout();
Dog dog = new Dog();
dog.shout();
Cat cat = new Cat();
cat.shout();
}
}
在本例中,Animal类是其余三个类的父类,在Animal中定义了一个动物叫的方法。在其余各类中都继承了Animal类的这个方法,但是显然,不同的物种,他们所发出的声音也是不同的。狗是汪汪汪,猫是喵喵喵,人则是直接rap起来。但是如果不对继承来的shout()方法加以改造,当然是行不通的,因为不可能所有的动物都是一样的叫声。当我们对这些继承来的方法加以改造就成功的符合了当前的业务场景。这就是简单的多态应用。
以上是方法的多态,方法的重写和方法的重载都是多态的表现形式,下面说一下对象的多态
对象的多态主要表现为:
父类的引用指向子类的对象
在解释这个问题之前,我们先来了解一下什么是编译类型和运行类型。
首先,在Java中声明对象的格式是:
类名 对象名 = new 类名();
编译类型指的就是我们在等号左边所写的内容:类名 对象名。运行类型是指在等号右边所写的内容:new 类名();
我们的编译类型和运行类型其实是不一样的,编译类型是在定义对象的时候就已经确定好的。而我们的运行类型是可以改变的。
通过这个特点我们就可以使用对象的多态:
即:父类的引用指向子类的对象
比如我们声明一个类对象可以使用如下格式:
Animal dog = new Dog();
对象的引用是等号的左边,对象的真实类型是等号的右边。
那么我们这么写有什么好处呢?
不妨来看一个案例
class Instrument{
public void play() {
System.out.println("弹奏乐器");
}
public void outInfo() {
System.out.println("调用了父类的成员方法");
}
}
class Wind extends Instrument{
@Override
public void play() {
System.out.println("弹奏wind");
}
}
class Brass extends Instrument{
@Override
public void play() {
System.out.println("弹奏brass");
}
}
public class Music {
public static void main(String[] args) {
Instrument i1 = new Wind();
tune(i1);//调用演奏的方法
i1.outInfo();//调用父类的方法
Instrument i2 = new Brass();
tune(i2);//调用演奏的方法
}
public static void tune(Instrument i) {
i.play();
}
}
在上面的代码中,一共定义了三个类,一个乐器类Instrument充当父类,一个子类风琴类wind,一个子类Brass贝斯类。子类都重写了父类的play()方法,在主类Music里面又定义了一个tune的方法,注意,这里我们的参数类型是一个Instrument类型。在main函数里我们使用父类引用指向子类对象的方法去new了一个对象。当我们将该对象传入方法时,尽管和方法要求的参数类型Instrument不一致,仍然可以编译通过。以下是执行结果:
可以看到,我们通过父类引用子类对象传进方法的参数,自动识别并且调用了子类重写的父类方法。还可以调用父类的成员方法。我们不难得出结论:
当我们使用父类引用指向子类对象的时候,可以通过该种声明方法调用父类的成员方法和子类继承重写的父类方法。这在开发中给我们提供了很大便利。这种父类引用指向子类对象的声明方法被称为:向上转型。
向上转型是有局限性的,我们无法通过这种方式去调用子类的非继承成员方法,仍旧是上面的代码,我们添一点东西:
package com.lzl.day010.task;
class Instrument{
public void play() {
System.out.println("弹奏乐器");
}
public void outInfo() {
System.out.println("调用了父类的成员方法");
}
}
class Wind extends Instrument{
@Override
public void play() {
System.out.println("弹奏wind");
}
public void play2() {
System.out.println("调用wind的play2");
}
}
class Brass extends Instrument{
@Override
public void play() {
System.out.println("弹奏brass");
}
public void play2() {
System.out.println("调用brass的play2");
}
}
public class Music {
public static void main(String[] args) {
Instrument i1 = new Wind();
tune(i1);//调用演奏的方法
i1.outInfo();
Instrument i2 = new Brass();
tune(i2);//调用演奏的方法
}
public static void tune(Instrument i) {
i.play();
}
}
当我们在子类中补上play2()的成员方法时,在测试类中却无法调用,如下图所示
可以看到,在Eclipse给我们的提示中是没有play2()的方法可以给我们调用。想要解决这个问题,我们就不得不说一下向下转型:看代码
package com.lzl.day010.task;
class Instrument{
public void play() {
System.out.println("弹奏乐器");
}
public void outInfo() {
System.out.println("调用了父类的成员方法");
}
}
class Wind extends Instrument{
@Override
public void play() {
System.out.println("弹奏wind");
}
public void play2() {
System.out.println("调用wind的play2");
}
}
class Brass extends Instrument{
@Override
public void play() {
System.out.println("弹奏brass");
}
public void play2() {
System.out.println("调用brass的play2");
}
}
public class Music {
public static void main(String[] args) {
Instrument i1 = new Wind();//父类引用指向子类对象
Wind wind = (Wind)i1;//向下转型
wind.play2();
tune(wind);//调用演奏的方法
i1.outInfo();
Instrument i2 = new Brass();
tune(i2);//调用演奏的方法
}
public static void tune(Instrument i) {
i.play();
}
}
我们通过对父类引用指向子类对象的i1做一个强制类型转换的声明方法。当我们再次调用子类的play2()方法时,发现就可以调用了,并且当我们将wind对象传入tune方法时,发现仍然可以调用,至此向下转型完成。
向下转型可以完成对父类成员变量和子类继承重写父类方法,以及子类独有的成员方法的调用。
至此多态详解完成
小白一个,还请各位大佬批评指正