Java的多态
Java的多态
同种类型的对象,表现出的不同的形态
一、多态的表现形式
父类类型 对象名称 = 子类对象;
二、多态的前提
- 有继承/实现关系
- 有父类引用指向子类对象
- 有方法的重写
好处:使用父类型作为参数,可以接收所有的子类对象,体现多态的扩展性与便利
三、多态调用成员的特点
- 变量调用:编译看左边,运行也看左边
- 方法调用:编译看左边,运行看右边
多态调用成员的内存图解:
- 测试类
Test
的字节码文件先加载到方法区中,虚拟机会自动的调用main
方法加载到栈 - 第一行代码,用到了两个类,那必定是先加载父类的字节码文件此处应该还有
Object
类的字节码文件,但是此处用不到,就省略了,所以先加载Animal
类的字节码文件,并挂上虚方法表;接下来加载Dog
类的字节码文件,与此同时会把父类的虚方法表继承下来,并把show
方法进行一个覆盖,并建立联系(Dog
类会记录自己的父类Animal
类字节码文件的位置) - 第一行代码,左边
Animal a
会在栈区开辟一个小空间,右边遇见new
关键字,会在堆区开辟一个空间,一部分存储从父类继承下来的成员变量的信息,另一部分则存储自己的成员变量的信息,赋值并将地址值传给栈区的变量a
的空间中 - 第二行代码,用多态方法调用成员变量,(编译看左边)虚拟机会先看栈区空间的父类区域,若有该成员变量,则编译成功,否则编译失败,(运行也看左边)且运行也会直接看父类空间中的
name
的值,并获取打印出来【若没有使用多态创建对象,调用对象会首先在子类区域寻找,没有才会从父类中寻找】 - 第三行代码,用多态方式调用成员方法,(编译看左边)在编译的时候会先去找父类里面有没有
show
方法,如果没在父类中没找到则会报错,(运行看右边)在实际运行时看的是子类的show
方法(虚方法表中),所以子类的show
方法会被加载进栈,并运行
示例代码:
public class Test {
public static void main(String[] args) {
// 创建对象(多态方式)
Animal a = new Dog();
// 调用成员变量时:编译看左边,运行也看左边
// 编译看左边:javac编译代码的时候,会看左边的父类中有没有这个变量,如果有,编译成功,如果没有编译失败
// 运行也看左边:java运行代码的时候,实际获取的就是左边父类中成员变量的值
System.out.println(a.name); // 动物
// 调用成员方法:编译看左边,运行看右边
// 编译看左边:javac编译代码的时候,会看左边的父类中有没有这个方法,如果有,编译成功,如果没有则编译失败
// 运行看右边;java运行代码的时候,实际上运行的是子类中的方法
a.show(); // Dog——show方法
// 成员变量:a是Animal类型的,所以默认都会从Animal这个类中去找
// 成员方法:如果子类对方法进行了重写,那么在虚方法表中是会把父类中的方法进行覆盖的
}
}
class Animal {
String name = "动物";
public void show() {
System.out.println("Animal——show方法");
}
}
class Cat extends Animal{
String name = "小猫";
@Override
public void show() {
System.out.println("Cat——show方法");
}
}
class Dog extends Animal{
String name = "小狗";
@Override
public void show() {
System.out.println("Dog——show方法");
}
}
四、多态的优势和弊端
优势:
-
方法中,使用父类型作为参数,可以接收所有子类对象
-
在多态形式下,右边对象可以实现解耦合,便于扩展和维护
Person p = new Student ();
p.work(); // 业务逻辑发生改变时,后序代码无需修改
- 定义方法时,使用父类型作为参数,可以接收所有子类对象,体现多态的扩展性与便利
弊端:
- 不能使用子类的特有功能
若要使用子类的特有功能则必须进行类型转换
引用数据类型的类型转换:
- 自动类型转换:(低转高)
Animal a = new Dog();
- 强制类型转换:(高转低)
Dog b = (Dog)a;
进行强制类型转化时,可以使用instanceof
关键字来判断和转换(如下示例代码中)
强制类型转换能解决的问题:
- 可以转换成真正的子类类型,从而调用子类独有的功能
- 转换类型与真实对象类型不一致会报错
- 转换的时候用
instanceof
关键字进行判断
示例代码:
public class Test {
public static void main(String[] args) {
// 自动类型转化
Animal a = new Dog();
a.eat(); // 输出: 狗吃骨头
// 多态的弊端
// a.lookhome()不能调用子类的特有功能
// 因为在多态中方法的调用是编译看左边————而左边父类中没有子类的特有方法,导致编译失败
// 若要实现只能通过强制类型转化(但转化的类型必须对应好)
Dog b = (Dog)a;
b.lookhome(); // 输出: 狗在看家
// 可以通过instanceof判断类型在进行强转
if (a instanceof Dog) {
Dog c = (Dog)a;
c.lookhome(); // 输出: 狗在看家
}else if (a instanceof Cat) {
Cat c = (Cat)a;
c.catchmouse(); // 输出: 狗在看家
}else {
System.out.println("没有此类型,无法转换");
}
// JDK14之后instanceof出了更简洁的方法
if (a instanceof Dog d) {
d.lookhome();
}else if (a instanceof Cat d) {
d.catchmouse();
}else {
System.out.println("没有这个类型,无法转换");
}
}
}
class Animal {
public void eat() {
System.out.println("动物在吃东西");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
public void lookhome() {
System.out.println("狗在看家");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫在吃小鱼干");
}
public void catchmouse() {
System.out.println("猫正在抓老鼠");
}
}
五、综合练习:
Animal
类:
public class Animal {
private int age;
private String color;
public Animal() {
}
public Animal(int age, String color) {
this.age = age;
this.color = color;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setColor(String color) {
this.color = color;
}
public String getColor() {
return color;
}
public void eat(String something) {
System.out.println("动物在吃" + something);
}
}
Person
类:
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void keepPet(Animal an, String something) {
if(an instanceof Dog d) {
System.out.println("年龄为" + age + "岁的" + name + "养了一只" + an.getColor() + "的" + an.getAge() + "岁的狗");
d.eat("骨头");
d.lookhome();
} else if (an instanceof Cat c) {
System.out.println("年龄为" + age + "岁的" + name + "养了一只" + an.getColor() + "的" + an.getAge() + "岁的猫");
c.eat("鱼");
c.catchMouse();
} else {
System.out.println("没有这种动物");
}
}
}
Dog
类:
public class Dog extends Animal{
public Dog() {
}
public Dog(int age, String color) {
super(age, color);
}
public void lookhome() {
System.out.println("狗在看家");
}
public void eat(String something) {
System.out.println(getAge() + "岁的" + getColor() + "的狗两只前腿死死的抱住" + something + "猛吃");
}
}
Cat
类:
public class Cat extends Animal{
public Cat() {
}
public Cat(int age, String color) {
super(age, color);
}
public void catchMouse() {
System.out.println("猫在逮老鼠");
}
public void eat(String something) {
System.out.println(getAge() + "岁的" + getColor() + "的猫眯着眼睛侧着头吃" + something);
}
}
测试类:
public class Test {
public static void main(String[] args) {
Person p1 = new Person("老王", 30);
Dog d = new Dog(2, "黑颜色");
p1.keepPet(d, "骨头");
Person p2 = new Person("老李", 25);
Cat c = new Cat(3, "灰颜色");
p2.keepPet(c, "鱼");
}
}
运行结果: