多态是继封装、继承之后,面向对象的第三大特性。
现实事物经常会体现出多种形态,如学生,学生是人的一种,则一个具体的同学张三既是学生也是人,即出现两种形态。
Java作为面向对象的语言,同样可以描述一个事物的多种形态。如Student类继承了Person类,一个Student的对象便既是Student,又是Person。
Java中多态的代码体现在一个子类对象(实现类对象)既可以给这个子类(实现类对象)引用变量赋值,又可以给这个子类(实现类对象)的父类(接口)变量赋值。
如Student类可以为Person类的子类。那么一个Student对象既可以赋值给一个Student类型的引用,也可以赋值给一个Person类型的引用。
最终多态体现为父类引用变量可以指向子类对象。
1. 多态的前提是必须有子父类关系或者类实现接口关系,否则无法完成多态。
2. 在使用多态后的父类引用变量调用方法时,会调用子类重写后的方法。
通用格式:
父类类型 变量名 = new 子类类型(); //定义格式
变量名.方法名(); //使用格式
父类 变量名 = new 子类();
如: class Fu {}
class Zi extends Fu {}
//类的多态使用
Fu f = new Zi();
抽象类 变量名 = new 抽象类子类();
如: abstract class Fu {
public abstract void method();
}
class Zi extends Fu {
public void method(){
System.out.println(“重写父类抽象方法”);
}
}
//类的多态使用
Fu fu= new Zi();
接口 变量名 = new 接口实现类();
如: interface Fu {
public abstract void method();
}
class Zi implements Fu {
public void method(){
System.out.println(“重写接口抽象方法”);
}
}
//接口的多态使用
Fu fu = new Zi();
同一个父类的方法会被不同的子类重写。在调用方法时,调用的为各个子类重写后的方法。
如 Person p1 = new Student();
Person p2 = new Teacher();
p1.work(); //p1会调用Student类中重写的work方法
p2.work(); //p2会调用Teacher类中重写的work方法
因此,当变量名指向不同的子类对象时,由于每个子类重写父类方法的内容不同,所以会调用不同的方法。
当进行多态的定义后,那么多态出现后类的成员有啥变化呢?我们知道继承时,子父类之间成员变量有了自己的特定变化,那么当多态出现后,成员变量在使用上有没有变化呢?
首先我们看成员变量的变化:
看如下代码:思考下输出结果为什么?
class Fu {
int num = 4;
}
class Zi extends Fu {
int num = 5;
}
class Demo {
public static void main(String[] args) {
Fu f = new Zi();
System.out.println(f.num);
Zi z = new Zi();
System.out.println(z.num);
}
}
输出结果为:
4 //调用的是父类中的成员变量
5 //这里不属于多态调用,因此输出该类的成员变量
我们在看以下代码,注意代码变化:思考下代码输出结果是什么?
class Fu {
//int num = 4; -----------我把这行代码注释掉
}
class Zi extends Fu {
int num = 5;
}
class Demo {
public static void main(String[] args) {
Fu f = new Zi();
System.out.println(f.num); // ---------f.num无法通过编译
Zi z = new Zi();
System.out.println(z.num);
}
}
其实以上代码无法编译通过,大家可以试试。我这就不截图了。
因此,我们可以总结以下规律:
多态调用时,同调用成员变量相似,多态出现后会导致子父类中的成员方法也会有微弱的变化。
看如下代码:思考输出结果?
class Fu { //定义一个父类
int num = 4;
void show() { //定义一个父类成员方法
System.out.println("Fu show num");
}
}
class Zi extends Fu { //定义一个子类,子类继承父类
int num = 5;
void show() { //子类重写父类的show方法
System.out.println("Zi show num");
}
}
class Demo { //测试类
public static void main(String[] args) {
Fu f = new Zi();
f.show(); //思考输出结果?
}
}
输出结果为:
Zi show num //结果执行的是子类的重写方法
继续看如下代码:注意代码的变化!
class Fu {
int num = 4;
/*
void show() {
System.out.println("Fu show num");
}
*/
}
class Zi extends Fu {
int num = 5;
void show() {
System.out.println("Zi show num");
}
}
class Demo {
public static void main(String[] args) {
Fu f = new Zi();
f.show(); //这里会编译失败:会显示Fu类没有show方法,编译无法通过
}
}
**输出结果是什么呢?**同样,编译失败!大家自己试试。
因此,我们可以总结以下规律:
1. 子类重写了父类该成员方法,则直接调用子类中的成员方法;
2. 子类没有重写父类的该成员方法,则直接调用父类中的成员方法。
多态产生的问题:引用类型的实际对象类型的疑惑问题
看如下代码:思考!
public abstract class Person { //这里定义一个抽象Person父类
public abstract void sleep();
}
public class Student extends Person { //这里定义一个Student的子类
public void sleep(){
System.out.println("学生在休息睡觉");
}
public void study(){
}
}
public class Teacher extends Person{ //这里定义第二个子类Teacher子类
public void sleep(){
System.out.println("老师在休息");
}
}
接下来我们看这样的一个测试类:
public class Test {
public static void main(String[] args) {
Person p1 = new Student();
Person p2 = new Teacher();
}
}
思考一下:Person p1 = new Student();
和Person p2 = new Teacher();
由于多态的产生,我们如何分辨这两个引用变量的所引用的实际对象类型,因此Java中利用instanceof
关键字来判断引用类型指向的对象的实际类型。
那么我们可以利用关键字来判断对象的实际类型,然后调用它们的对应的方法
public class Test {
public static void main(String[] args) {
Person p1 = new Student();
Person p = new Teacher();
if(boolean b = p instanceof Student){
System.out.println(b);
p.sleep();
}
if(boolean b = p instanceof Teacher){
System.out.println(b);
p.sleep();
}
}
}
实际上多态定义过程中,涉及了一个数据转型的问题。我们知道多态的定义格式为一个父类变量的引用指向子类的对象来完成,既父类类型 变量名 = new 子类类型();
,这里就同基础数据类型一样,涉及了数据转型问题。
父类类型 变量名 = new 子类类型();
如:Person p = new Student();
向上转型我们很容易理解,接下来我们讲另一种转型。
背景:为什么需要向下转型?认真看!
我们前面说过多态调用成员方法和变量。当调用成员方法时,如果父类中含有该成员方法,且该方法被子类重写后,那么调用的是子类重写的方法,而如果父类中没有该方法时,则无法编译。对于调用成员变量,无论编译还是运行都是根据父类的成员变量来编译和运行,跟子类无关。
我们考虑这种情况:如果我们需要调用子类的自身的方法或成员变量那该怎么办呢,调用的方法或成员变量由子类独有?我们应该怎么实现呢?
如以下代码:
public abstract class Person { //这里定义一个抽象Person父类
int i =10 ;
public abstract void sleep();
}
public class Student extends Person { //这里定义一个Student的子类
String name = "凝练于心";
public void sleep(){
System.out.println("学生在休息睡觉");
}
public void study(){
System.out.println("学生在学习java");
}
}
我们如何利用多态调用子类study()
方法呢?
我们接下来看以下代码:
public class Test {
public static void main(String[] args) {
Person p = new Student();
p.sleep(); //这里调用的是子类Student的sleep()方法
//p.study(); 注意这里如果这样调用是无法通过编译的!
//这里就需要利用向下转型了
Student s = (Student) p;
p.study(); //这里就可以调用了
p.name;
}
}
子类类型 变量名 = (子类类型) 父类类型的变量;
如:Student stu = (Student) p; //变量p 实际上指向Student对象
我们定义两个类:一个是孔子爹这个类;一个为孔子类
public class 孔子爹 {
int 年龄 = 80岁;
public void 杀猪(){
System.out.println("孔子爹是个杀猪的,会杀猪")
}
}
public class 孔子{
int 年龄 = 48岁;
public void 教论语()
System.out.println("孔子是个老师,会教论语")
}
}
假设有一天孔子的邻居请孔子爹去杀猪,但孔子爹出差去了不在家,由于是之前答应好的,古人讲究诚信,因此孔子就假装他爹去帮他邻居杀猪,但是他怕他邻居认出来,他就化了个装,去帮他邻居杀猪。杀猪完后他就卸妆去学院给他的学生讲课。接下来我们就用多态的过程来实现孔子的一天的工作!
public class 孔子的一天{
//孔子要去杀猪了
//开始化妆 :
孔子爹 fakeDade = new 孔子(); //这里利用向上转型,来装爹
//成爹的工作:
fakeDade.杀猪();//帮邻居杀猪
//孔子要去教学了
//卸妆 :
trueSon = (孔子)fakeDade; //这里利用向下转型,恢复模样
//成自己工作
trueSon.教论语(); //给学生上课,调用子类孔子自己的方法