Java多态
一、多态概述
多态作为面向对象程序设计的三大支柱其中之一(另外两个分别为封装和继承),在Java中同样占有很重要的位置。下面就将简要介绍多态的含义以及使用的注意事项。
多态:基类型对象访问派生类重写的方法。由此可以看出要想实现多态,必须满足三个条件:
1、存在继承关系
2、派生类中要有重写的基类的方法(且只能调用重写的方法)
3、基类的引用对象指向派生类
提供一个多态的例子:
public class PolymorphisDemo{
/** 主方法 **/
public static void main(String[] args){
displayObject(new Circle(1,"red",false));
displayObject(new Rectangle(1,1,"black",true));
}
public static void diplayObject(GeometricObject object){
System.out.println("Created on " + object.getDateCreated() + ". Color is " + object.getColor());
}
}
输出为:
Created on Mon May 09 19 : 25 : 20 EDT 2022. Color is red
Created on Mon May 09 19 : 25 : 20 EDT 2022. Color is black
Circle类和Rectangle类均为子类,而GeometricObject类为父类,当使用displayObject方法时,父类型变量引用子类型变量便意味着多态
二、多态的动态绑定机制
1、动态绑定
Object o = new GeometricObject();
System.out.println(o.toString());
在上述代码段中,o应该调用Object类的toString方法还是GeometricObject类的toString方法呢?我们先搞清楚两个概念:声明类型和实际类型。
声明类型即为变量在声明时的类型,o的声明类型为Object类
实际类型为变量被引用时实际的类型,o的实际类型为GeometricObject类
而o调用的方法是由其的实际类型决定的,即o调用GeometricObject类的toString方法,此便是动态绑定。
动态绑定:父类引用指向的或者调用的方法是子类的方法
2、向上转型
public class Human {
public void sleep() {
System.out.println("Human sleep..");
}
public static void main(String[] args) {
Male m = new Male();
m.sleep();
Human h = new Male();// 向上转型
h.sleep(); // 动态绑定
// h.speak(); 此方法不能编译,报错说Human类没有此方法
}
}
class Male extends Human {
@Override
//重写sleep方法
public void sleep() {
System.out.println("Male sleep..");
}
//子类独有的speak方法(父类引用不能调用)
public void speak() {
System.out.println("I am Male");
}
}
输出结果为
//子类本身使用sleep方法
Male sleep..
//动态绑定后父类引用使用子类重写的sleep方法
Male sleep..
由此可以看出向上转型父类引用不能调用子类自己的方法,只能调用子类重写的父类的方法。
3、向下转型
public class Human {
public void sleep() {
System.out.println("Human sleep..");
}
public static void main(String[] args) {
//向上转型
Male m1 = new Male();
Human h1 = m1;
h1.sleep();
//h1.speak(); // 此时需要向下转型,否则不能调用speak方法。
// //向下转型
// Male m2 = new Male();
// Human h2 = m2;
// m2 = (Male) h2; //即为显式转换
// m2.speak();
// //向下转型:失败
// Human h3 = new Human();
// Male m3 = (Male)h3;
// m3.speak(); //此时会出现运行时错误,所以可以用instanceOF判断
//
// //向下转型:类型防护
// Human h4 = new Human();
// if (h4 instanceof Male){ // 因为h4不是Male的实例,所以不执行if内部代码
// Male m4 = (Male)h4;
// m4.speak();
// }
}
}
class Male extends Human {
@Override
//重写sleep方法
public void sleep() {
System.out.println("Male sleep..");
}
//独有的speak方法
public void speak() {
System.out.println("I am Male");
}
}
三、多态的主要应用场景
1、循环调用基类对象,访问不同派生类对象
我们用一个例子来表示:
Grade[] tests = new Grade[3];
// 第一次考试采用五级计分制,考了75
tests[0] = new Grade();
tests[0].setScore(75);
// 第二次考试采用二级计分制(P或者F)。总共20题,每题分值相同,考生答错5题。
// 通过的最低分数线是60分
tests[1] = new PassFailExam(20, 5, 60);
// 第三次是期末考试也采用五级计分制. 总共50题,每题分值相同,考试答错7题
tests[2] = new FinalExam(50, 7);
// 显示每次考试的分数和等级
for(int i=0; i<tests.length; i++){
System.out.println("Score: " + tests[i].getScore()+ " " +
"Grade: " + tests[i].getGrade());
}
2、实参作为派生类,形参作为基类
我们也用一个例子表示一下:
public static void main(String[] args) {
Grade[] tests = new Grade[3];
// 第一次考试采用五级计分制,考了75
tests[0] = new Grade();
tests[0].setScore(75);
// 第二次考试采用二级计分制(P或者F)。总共20题,每题分值相同,考生答错5题。
// 通过的最低分数线是60分
tests[1] = new PassFailExam(20, 5, 60);
// 第三次是期末考试也采用五级计分制. 总共50题,每题分值相同,考试答错7题
tests[2] = new FinalExam(50, 7);
// 显示每次考试的分数和等级
for(int i=0; i<tests.length; i++){
showValue(tests[i]);
}
}
//形参为基类
public static void showValue(Grade exam){
System.out.println("Score: " + exam.getScore()+ " " +
"Grade: " + exam.getGrade());
}
四、多态的对象转换
对象转换:一个对象的引用类型转换为另一个对象的引用
m(new Student());
等价于
Object o = new Student();//隐式转换
m(o);
而显式转换则在阐述向下转型时已经详细演示了
Male m2 = new Male();
Human h2 = m2;
m2 = (Male) h2; //即为显式转换
m2.speak();
Human h3 = new Human();
Male m3 = (Male)h3;
m3.speak(); //此时会出现运行时错误,所以在显式转换之前可以用instanceOF判断
Human h4 = new Human();
if (h4 instanceof Male){ // 因为h4不是Male的实例,所以不执行if内部代码
Male m4 = (Male)h4;
m4.speak();
}
五、instanceof关键字
instanceof的作用:用来判断一个对象是否是另一个对象的实例
下面我们看一段代码
Object o = new GeometricObject();
if(o instanceof GeometricObject){
System.out.print("yes");
}
在这里我们用instance判断o是不是son的实例,可以看出,o指向的是子类对象GeometricObject,所以返回为true,可以实现输出yes。
下面再看一段和上面相似的代码
Object o = new Object();
if(o instanceof GeometricObject){
System.out.print("yes");
}
可以看出,这个o声明和实际类型都是Object,o指向的是父类对象Object,与GeometricObject没有关系,因此这里返回false,不会输出yes,如果不判断,就会抛出ClassCastException异常