面向对象——多态
现实事物经常会体现出多种形态,如学生,学生是人的一种,则一个具体的同学张三既是学生也是人,即出现两种形态。
概述
Java中多态的代码体现在一个子类对象(实现对象)既可以给这个子类(实现类对象)引用变量赋值,又可以给这个子类(实现类对象)的父类(接口)变量赋值。
多态体现为
父类引用变量可以指向子类对象
多态的前提是必须有子父类关系或者类实现接口关系,否则无法完成多态。
在使用多态后的父类引用变量调用方法时,会调用子类重写后的方法。
多态的定义与使用格式
多态的定义格式
就是父类的引用变量指向子类对象
父类类型 变量名 = new 子类类型();
变量名.方法名();
普通类多态定义格式
父类 变量名 = new 子类();
package com.xyz.www;
public class Fu_101 {
class Zi extends Fu_101 {
// 类的多态使用
Fu_101 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 接口实现类();
package com.yzx.www;
public interface Fu {
public abstract void method();
}
class Zi implements Fu {
public void method() {
System.out.println("重写接口抽象方法");
}
}
//接口的多态使用
class F1 {
public static void main(String[] args) {
Fu fu = new Zi();
}
}
注意事项
同一个父类的方法会被不同的子类重写,在调用方法时,调用的为各个子类重写后的方法。
eg:
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);
}
}
当父类中出现同名的成员变量时,多态调用该变量时
编译时期
参考的是引用型变量所属的类中是否有被调用的成员变量。没有,编译失败。
运行时期
也是调用引用型变量所属的类中的成员变量。
简单记
编译和运行都参考等号的左边。编译运行看左边。
多态成员方法
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();
}
}
编译时期
参考引用变量所属的类,如果没有类中没有调用的方法,编译失败。
运行时期
参考引用变量所指的对象所属的类,并运行对象所属类中的成员方法。
简而言之
编译看左边,运行看右边。
instanceof--关键字
通过instanceof关键字判断某个对象是否属于某种数据类型,如:学生的对象属于学生类,学生的对象也属于人类。
格式
boolean b = 对象 instanceof 数据类型;
eg:
Person p1 = new Student(); // 前提条件,学生类已经继承了人类
boolean flag = p1 instanceof Student; //flag结果为true
boolean flag2 = p2 instanceof Teacher; //flag结果为false
多态-转型
向上转型
当有子类对象赋值给一个父类引用时,便向上转型,多态本身就时向上转型的过程。
使用格式:
父类类型 变量名 = new 子类类型();
eg:
Person p = new Student();
向下转型
一个已经向上转型的子类对对象可以使用强制类型转换的格式,将父类引用转换为子类引
用,这个过程叫向下转型
使用格式:
子类类型 变量名 = (子类类型) 父类类型的变量;
eg:
Student stu = (Student) p; //变量p 实际上指向Student对象
多态的好处和弊端
好处
向上转型隐藏了子类类型,提高了代码的扩展性。
坏处
只能使用父类共性的内容,而无法使用子类特有的功能,功能有限。
//描述动物类,并抽取共性eat方法
abstract class Animal {
abstract void eat();
}
// 描述狗类,继承动物类,重写eat方法,增加lookHome方法
class Dog extends Animal {
void eat() {
System.out.println("啃骨头");
}
void lookHome() {
System.out.println("看家");
}
}
// 描述猫类,继承动物类,重写eat方法,增加catchMouse方法
class Cat extends Animal {
void eat() {
System.out.println("吃鱼");
}
void catchMouse() {
System.out.println("抓老鼠");
}
}
public class Test {
public static void main(String[] args) {
Animal a = new Dog(); //多态形式,创建一个狗对象
a.eat(); // 调用对象中的方法,会执行狗类中的eat方法
// a.lookHome();//使用Dog类特有的方法,需要向下转型,不能直接使用
// 为了使用狗类的lookHome方法,需要向下转型
// 向下转型过程中,可能会发生类型转换的错误,即ClassCastException异常
// 那么,在转之前需要做健壮性判断
if( !a instanceof Dog){ // 判断当前对象是否是Dog类型
System.out.println("类型不匹配,不能转换");
return;
}
Dog d = (Dog) a; //向下转型
d.lookHome();//调用狗类的lookHome方法
}
}
总结
什么时候使用向上转型
当不需要面对子类类型时,通过提高扩展性,或者使用父类的功能就能完成相应的操作,这时就可以使用向上转型。
eg:
Animal a = new Dog();
a.eat();
什么时候使用向下转型
当要使用子类特有功能时,就需要使用向下转型。
eg:
Dog d = (Dog) a; //向下转型
d.lookHome();//调用狗类的lookHome方法
向下转型的好处
可以使用子类特有功能。
弊端
需要面对具体的子类对象;在向下转型时容易发生ClassCastException类型转换异常。在转换之前必须做类型判断。
eg:
if( !a instanceof Dog){
…
}