通过前面的学习了解,我们知道Java是面向对象的编程,而面向编程有三大特性:封装、继承与多态。其中封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据。对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法。继承是为了重用父类代码,当多个子类去继承父类成员时,这些成员可以在子类多次使用,这就是解决了程序的复用性问题。同时继承也为多态做了很多铺垫,那什么是多态呢?多态的实现机制又是什么呢?下面为大家一一揭开:
一、 什么是多态?
通过一个例子:比如你是一个酒神,对酒独有情中,某日回家发现几个杯子都装了白酒,从外面看我们是不可能知道这些是什么酒,只有喝了之后才能猜出是何种酒,你一喝,这是剑南春、再喝这是五粮液,再喝这是酒鬼酒...在这里我们可以描述成如下:
这里所表现的就是多态。剑南春、五粮液、酒鬼酒都是酒的子类,我们只是通过酒这一个父类就能够引出不同的子类,这就是多态。
简单的说:
我们只有在运行的时候(也就是在喝某杯酒的时候)才知道引用变量所指向的具体实例对象(也就是具体是啥酒),多态就是:同一个引用类型,使用不同的实例而执行不同的操作。(多种运行状态)
如果细致的说:
1、所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用,在编程时并不确定,而是在程序中运行期间才确定。
2、即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
3、因为在程序运行时才确定具体类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
二、怎么实现多态?
1、首先我创建一个叫"主人"Master的类,他喜欢养宠物(Pet)类,他养了Dog(狗)类、Cat(猫)类,那他每天要做的事就是喂猫或者是喂狗。现在我要去用程序去描述这个事情,如下:
(1)首先新建一个模块叫day_1109,再在其src目录下创建一个包叫com.feisi.demo(关于这些模块和包的内容可以去看我的博客),再创建一个类叫"宠物类"Pet类,并把它变成抽象类(因为宠物是猫和狗抽离出来的特性)。再在里面定义一个"吃"eat()抽象方法,因为不同的宠物吃的啥不一样。
package com.feisi.demo;
//父类:抽象类
public abstract class Pet {
public abstract void eat();
}
(2)再在包下创建一个"猫"Cat类,让它去继承"宠物类"Pet类,那就必须去实现其父类的抽象方法。(若不太明白去我博客看)
package com.feisi.demo;
public class Cat extends Pet{
//子类覆盖父类的方法
@Override
public void eat() {
System.out.println("猫吃鱼.....");
}
}
(3))再在包下创建一个"狗"Dog类,让它也去继承"宠物类"Pet类,也必须实现其父类的抽象方法。
package com.feisi.demo;
public class Dog extends Pet{
@Override
public void eat() {
System.out.println("狗吃肉.....");
}
}
(4)再创建一个"主人"Master类,因为他每天都要去喂宠物,就创建了一个方法"喂养"feed()方法,因为得告诉我喂哪一只狗或者哪一只猫,所以参数列表传进的值应该是一个对象。
package com.feisi.demo;
public class Master {
public void feed(Dog dog){
dog.eat();//喂狗
}
public void feed(Cat cat){
cat.eat();//喂猫
}
}
此时我会发现:这两个feed()方法的方法名、返回值相同,而它的参数列表不同,这就是典型的方法的重载。这时就把这个题的整体设计出来了。
但是那如果这个人他又想去养一些其他的宠物,比如仓鼠,那是不是又要写一个叫"仓鼠"的类,那意味者我们的feed()方法是不是又要添加一个,传进一个仓鼠对象。过几天他兴趣大发,又要养一些其他的宠物,是不是又要去像之前一样一个一个添加这些?那么就会发现随着时间的推移,意味着我们需要在类里面不断的需要去重载,那这样就会很麻烦。
(5)于是乎我们就想到可以这么写:那么既然猫、狗、仓鼠等等...它们都属于"宠物"Pet类,那我们可以把feed()方法改变一下:(这时我们的方法比之前写的涵盖的范围比之前更加的广泛)
package com.feisi.demo;
public class Master {
//方法的拓展性更广泛
//Pet:参数对象是具有多样性(多态性:Dog、Cat、Tiger....)
public void feed(Pet pet) {
//编译时多态:对象调用的方法在编译时属于哪个类,则调用哪个类的方法。
pet.eat(); //这里表面上是父类调用的方法,也就是父类Pet调用的方法
}
/*
public void feed(Dog dog){
dog.eat();//喂狗
}
public void feed(Cat cat){ //将前面写的方法注释掉
cat.eat();//喂猫
}
*/
}
这下就算你养的再多的宠物,是不是都只要它是宠物,喂养它就只需要这一个方法便可以完成。
(6)接下来我们再写一个测试类Test,在里面创建一个主人对象,一个狗对象和一个猫对象,(在继承时父类不能调用子类方法,但是子类可以调用父类的方法)
package com.feisi.demo;
public class Test {
public static void main(String[] args) {
Master master =new Master();
Dog dog =new Dog();
Cat cat =new Cat();
}
}
既然狗和猫都是属于宠物,那我们能不能这样写,把它分别改成:
package com.feisi.demo;
public class Test {
public static void main(String[] args) {
Master master =new Master();
//父类的引用,指向子类对象(也就是父类的引用,子类的实例)
Pet dog =new Dog();
//父类的引用,指向子类对象(也就是父类的引用,子类的实例)
Pet cat =new Cat();
master.feed(dog);
master.feed(cat);
}
}
(7)代码运行结果为:(结果很惊奇,为啥这样就实现了我们实际的需要呢)
虽然我们feed()方法里的参数,它编译时是Pet类对象调用的方法,但实际上运行时却是各个子类对象调用了该方法。那为啥呢?
(8)那我以这里面的其中一个举例:Pet dog = new Dog();(用一个类的运行图解释)
这时注意一个问题:虽然我们在编译时的的确确因为参数是Pet 类型的,所以我们是Pet类对象调用的方法,编译时多态:程序编译时,对象属于哪个类就调用哪个类的方法。但是我们实际上运行时它需要先去找内存,找到一个叫Dog的类对应的实际对象类型,那么它的原型指向的就是Dog类的eat()方法,同样的道理,猫也是一样。这就是运行时多态:在程序中运行时,根据父类的引用在堆中找到对应的对象的实际类型,再调用该类型的方法。
三、为什么使用多态?
1、程序的拓展性问题。
四、总结怎么实现多态?
1、多个子类重写父类的方法。
2、父类的引用指向子类的对象:
这里就可以再把之前在给feed()方法传入参数时的代码更加简单化:
然后Master类里面的feed()方法里的参数意思变成是Pet pet =new Dog() 或者是Pet pet =new Cat () ,其实是一回事。
五、最后留一道练习题给大家作为巩固知识哈哈!(下方附一张图片,我的参考答案我会在下一期进行公布,当然那是我的做法,各位肯定有更多的新颖的想法)