多态

多态: polymorphism

概述

有了封装才有面向对象, 有了面向对象才有继承和多态.

什么是多态: 同类型的对象, 表现出的不同形态.


图1

多态的表现形式: 父类类型 对象名称 = 子类对象;

多态的前提:

  1. 有继承或实现关系 (实现与接口有关)

  2. 有父类引用指向子类对象, Fu f = new Zi();

  3. 要有方法重写.

程序示例:

父类:

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 show() {
System.out.println(name + ", " + age);
}
}

子类:

public class Administrator extends Person {
@Override
public void show() {
System.out.println("管理员的信息为: " + getName() + ", " + getAge());
}
}

子类:

public class Student extends Person {
@Override
public void show() {
System.out.println("学生的信息为: " + getName() + ", " + getAge());
}
}

子类:

public class Teacher extends Person {
@Override
public void show() {
System.out.println("老师的信息为: " + getName() + ", " + getAge());
}
}

测试类:

public class Test {
public static void main(String[] args) {
// 创建三个对象, 调用 register 方法
Student s = new Student();
s.setName("zhangsan");
s.setAge(23);
Teacher t = new Teacher();
t.setName("lisi");
t.setAge(24);
Administrator ad = new Administrator();
ad.setName("wangwu");
ad.setAge(25);
// 调用 register 方法
register(s);
register(t);
register(ad);
}
// 定义一个方法, 表示注册, 要求这个方法可以接收管理员对象、教师对象和学生对象
// 这就要求形参必须是三个类型的父类
public static void register(Person p) { // 当看见一个方法的形参是一个类, 那么可以给这个方法传递的实参除了这个类的对象, 还包括这个类的所有子类的对象
p.show(); // 可以根据传递进来的不同对象, 执行不同的 show() 方法
}
}

执行结果:

学生的信息为: zhangsan, 23
老师的信息为: lisi, 24
管理员的信息为: wangwu, 2

多态调用成员的规则:

调用成员变量: 编译看左边, 运行看左边.

调用成员方法: 编译看左边, 运行看右边.

程序示例:

public class Test {
public static void main(String[] args) {
// 用多态的方式创建对象
Animal a = new Dog(); // 这是自动类型转换
// 调用成员变量
// javac 编译时看左边的父类中有没有这个变量, 如果有, 编译成功, 如果没有, 编译失败
// java 运行时获取左边父类中成员变量的值
System.out.println(a.name); // Animal
// 调用成员方法
// javac 编译时看左边父类中有没有这个方法, 如果有, 编译成功, 如果没有, 编译失败
// java 运行时实际运行的是子类中的方法
a.show(); // 调用 Dog 类的 show() 方法.
// a 是 Animal 类型的, 所以用 a 调用成员变量或成员方法时, 默认从 Animal 这个类中去找.
// 用 a 调用成员变量时, 会把父类中的成员变量也继承下来, 父类里面有一个 name, 子类里面也有一个 name. 现在 a 是父类类型, 所以调用父类的 name
// 用 a 调用成员方法时, 如果子类中对方法进行了重写, 那么在子类中会对父类方法进行覆盖, 所以实际调用的是子类中重写的方法.
}
}
class Animal {
String name = "Animal";
public void show() {
System.out.println("调用 Animal 类的 show() 方法. ");
}
}
class Dog extends Animal {
String name = "Dog";
@Override
public void show() {
System.out.println("调用 Dog 类的 show() 方法. ");
}
}
class Cat extends Animal {
String name = "cat";
@Override
public void show() {
System.out.println("调用 Cat 类的 show() 方法. ");
}
}

子类不重写父类的方法:

public class Test {
public static void main(String[] args) {
// 用多态的方式创建对象
Animal a = new Dog(); // 这是自动类型转换
// 调用成员变量
// javac 编译时看左边的父类中有没有这个变量, 如果有, 编译成功, 如果没有, 编译失败
// java 运行时获取左边父类中成员变量的值
System.out.println(a.name); // Animal
// 调用成员方法
// javac 编译时看左边父类中有没有这个方法, 如果有, 编译成功, 如果没有, 编译失败
// java 运行时实际运行的是子类中的方法
a.show(); // 调用 Animal 类的 show() 方法.
// a 是 Animal 类型的, 所以用 a 调用成员变量或成员方法时, 默认从 Animal 这个类中去找.
// 用 a 调用成员变量时, 会把父类中的成员变量也继承下来, 父类里面有一个 name, 子类里面也有一个 name. 现在 a 是父类类型, 所以调用父类的 name
// 用 a 调用成员方法时, 如果子类中对方法进行了重写, 那么在子类中会对父类方法进行覆盖, 所以实际调用的是子类中重写的方法.
}
}
class Animal {
String name = "Animal";
public void show() {
System.out.println("调用 Animal 类的 show() 方法. ");
}
}
class Dog extends Animal {
String name = "Dog";
// @Override
// public void show() {
// System.out.println("调用 Dog 类的 show() 方法. ");
// }
}
class Cat extends Animal {
String name = "cat";
@Override
public void show() {
System.out.println("调用 Cat 类的 show() 方法. ");
}
}

内存分析:


图2

第一步, 测试类的字节码文件进入方法区.

第二步, 虚拟机自动调用 main() 方法, 所以 main() 方法进栈. 下面开始执行 main() 方法里面的第一行代码.

第三步, 执行语句 Animal a = new Dog();, 将类的字节码文件加载到内存中的时候, 永远是先加载父类, 再加载子类. 此处 Dog 类的父类是 Animal, Animal 类的父类是 Object. 因此此处先加载 Object, 然后加载 Animal, 然后加载 Dog. 在方法区中, Dog 这个字节码文件和其父类 Animal 的字节码文件之间还有一个联系, 即 Dog 会记住 Animal 的字节码文件在方法区中的位置. 等号的左边在栈中开辟了一个小空间, 即变量 a, 类型为 Animal, 等号的右边有关键字 new, 因此在堆空间中开辟了一个小空间, 假设这个空间的地址值为 001, 这个空间会被分为两部分, 一部分存储从父类中继承下来的成员变量信息, 另一部分用来存储自己的成员变量信息. 初始化完成后会把地址值 001 赋给变量 a, a 就可以通过 001 找到堆空间中的这个对象. 到这里这一条语句才算执行完毕.

第四步, 执行语句 System.out.println(a.name); 时用 a 调用变量 name. 用多态的方式定义的变量, 在编译和运行时都是看左边, 即看父类. 此处 a 通过 001 去看堆空间中的那部分内存中, 用来存储从父类继承下来的成员变量的那部分空间, 看这部分空间中有没有要调用的这个成员变量, 此处为 name, 如果有 name, 那么编译成功, 如果没有则编译失败. 运行时还是去这部分空间找到这个成员变量, 获取它的值. 如果上一条语句为 Dog d = new Dog();, 则用 d 访问成员变量 name 时, 即执行 d.name 时, 不会去存放父类成员变量的那部分空间去找 name, 而是去存放子类自己的成员变量的那部分空间去找 name. 如果没找到, 才会去存放父类的成员变量的那部分空间去找 name.

第五步, 执行语句 a.show();, 用多态的方式定义的对象, 当这个对象调用方法时, 编译看左边, 即看父类, 执行看右边, 即看子类. 编译的时候, 会去方法区的父类的字节码文件中查看有没有 show() 方法, 如果找到了, 不会报错, 代码正常. 如果没有找到, 代码报错. 实际运行时, 会在方法区的子类的字节码文件中查找 show() 方法. 而子类可以重写这个 show() 方法去覆盖从父类中继承来的 show() 方法, 因此不同的对象执行的是各个不同子类的自己的方法.

多态的优势

在多态形式下, 右边对象可以实现解耦合, 便于扩展和维护.

Person p = new Student;
p.work(); // 业务逻辑发生改变时, 后续代码无需修改

假如写了上述代码之后, 过了一段时间, 要求不要让学生来工作了, 要换成老师来工作, 那么只需要改第一行代码为 Person p = new Teacher;, 其他代码不用修改.

定义方法的时候, 使用父类型作为参数, 可以接收所有子类对象, 体现多态的扩展性与便利.

定义集合时, 没有指明泛型, 则所有类型都能放到这个集合中.


图3 定义集合时没有指明泛型

多态的弊端

不能调用子类特有的方法, 即不存在于父类但是存在于子类的方法. 因为用多态的方式定义的对象, 在调用一个方法时, 编译时看父类, 运行时看子类, 而父类中没有这个方法, 则编译时报错.

程序示例:

public class Test {
public static void main(String[] args) {
// 用多态的方式创建一个对象
Animal a = new Dog();
// 调用子类重写了父类的方法
a.eat(); // 狗吃骨头
// 调用父类的方法, 子类没有重写
a.sleep(); // sleep
}
}
class Animal {
public void eat() {
System.out.println("eat");
}
public void sleep() {
System.out.println("sleep");
}
}
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("猫抓老鼠");
}
}

尝试调用子类特有的方法, 发现没有:


图4

解决方案:

利用类型的强制转换, 将对象的类型由父类的类型转换为子类的类型. 父类的范围比子类的范围大, 就好像 int 的范围比 byte 的范围大.

程序示例:

Animal a = new Dog();
Dog d = (Dog) a; // 这个 d 对象就可以执行 Dog 类有而 Animal 类没有的方法

关键字 instanceof 用于检查一个对象是否是一个类的实例. 用法: 对象名 instanceof 类名, 返回 boolean 值.

程序示例:

Animal a = new Dog();
if (a instanceof Dog) {
Dog d = (Dog)a;
} else if (a instanceof Cat) {
Cat c = (Cat) a;
} else {
System.out.println("没有这个类型, 无法转换");
}

程序示例:

public class Test {
public static void main(String[] args) {
// 用多态的方式创建对象
Animal a = new Dog();
// Cat b = (Cat) a; // ClassCastException: class demo1.Dog cannot be cast to class demo1.Cat
if (a instanceof Dog) {
Dog d = (Dog) a;
} else if (a instanceof Cat) {
Cat c = (Cat) a;
} else {
System.out.println("没有这个类型, 无法转换");
}
}
}
class Animal {
String name = "Animal";
public void show() {
System.out.println("调用 Animal 类的 show() 方法. ");
}
}
class Dog extends Animal {
String name = "Dog";
@Override
public void show() {
System.out.println("调用 Dog 类的 show() 方法. ");
}
}
class Cat extends Animal {
String name = "cat";
@Override
public void show() {
System.out.println("调用 Cat 类的 show() 方法. ");
}
}

Java 14 开始添加了一个新特性, 可以将判断和强制转换合在一起写, 写成一行.

Animal a = new Dog();
// 先判断 a 是否为 Dog 类型, 如果是, 则将 a 强制类型转化为 Dog 类型并赋值给新建的一个变量 d, 如果不是则不强转
if (a instanceof Dog d) {
d.lookHome(); // d 对象调用 Dog 类独有的方法 lookHome()
} else if (a instanceof Cat c) {
} else {
System.out.println("没有这个类型, 无法转换");
}

练习:

根据需求完成代码:

1.定义狗类

属性: 年龄, 颜色

行为: eat(String something) 方法 (something 表示吃的东西)

行为: 看家 lookHome 方法 (无参数)

2.定义猫类

属性: 年龄, 颜色

行为: eat(String something) 方法 (something 表示吃的东西)

行为: 逮老鼠 catchMouse 方法 (无参数)

3.定义 Person 类饲养员

属性: 姓名, 年龄

行为: keepPet(Dog dog, String something) 方法, 功能: 喂养宠物狗, something 表示喂养的东西

行为: keepPet(Cat cat, String something) 方法, 功能: 喂养宠物猫, something 表示喂养的东西

生成空参有参构造, set 和 get 方法

4.定义测试类 (完成以下打印效果):

keepPet(Dog dog, String somethind) 方法打印内容如下:

年龄为 30 岁的老王养了一只黑颜色的 2 岁的狗

2 岁的黑颜色的狗两只前腿死死的抱住骨头猛吃

keepPet(Cat cat, String somethind) 方法打印内容如下:

年龄为 25 岁的老李养了一只灰颜色的 3 岁的猫

3 岁的灰颜色的猫眯着眼睛侧着头吃鱼

5.思考:
1.Dog 和 Cat 都是 Animal 的子类, 以上案例中针对不同的动物, 定义了不同的 keepPet 方法, 过于繁琐, 能否简化, 并体会简化后的好处?
2.Dog 和 Cat 虽然都是 Animal 的子类, 但是都有其特有方法, 能否想办法在 keepPet 中调用特有方法?

结构:


图5

代码:

Javabean 类:

public class Animal {
private int age;
private String color;
public Animal() {
}
public Animal(int age, String color) {
this.age = age;
this.color = color;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public void eat(String something) {
System.out.println("动物在吃" + something);
}
}
public class Dog extends Animal {
public Dog() {
}
public Dog(int age, String color) {
super(age, color);
}
@Override
public void eat(String something) {
System.out.println(getAge() + " 岁的" + getColor() + "颜色的狗两只前腿死死的抱住" + something + "猛吃");
}
public void lookHome() {
System.out.println("狗在看家");
}
}
public class Cat extends Animal {
public Cat() {
}
public Cat(int age, String color) {
super(age, color);
}
@Override
public void eat(String something) {
System.out.println(getAge() + " 岁的" + getColor() + "颜色的猫眯着眼睛侧着头吃" + something);
}
public void catchMouse() {
System.out.println("猫抓老鼠");
}
}
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(Dog dog, String something) {
System.out.println("年龄为 " + age + " 岁的" + name + "养了一只" + dog.getColor() + "颜色的 " + dog.getAge() + " 的狗.");
dog.eat(something);
}
public void keepPet(Cat cat, String something) {
System.out.println("年龄为 " + age + " 岁的" + name + "养了一只" + cat.getColor() + "颜色的 " + cat.getAge() + " 的猫.");
cat.eat(something);
}
}

测试类:

public class Test {
public static void main(String[] args) {
Person person1 = new Person("老王", 40);
Dog dog = new Dog(2, "黑");
person1.keepPet(dog, "骨头");
Person person2 = new Person("老李", 54);
Cat cat = new Cat(3, "灰");
person2.keepPet(cat, "鱼");
}
}

执行结果:

年龄为 40 岁的老王养了一只黑颜色的 2 的狗.
2 岁的黑颜色的狗两只前腿死死的抱住骨头猛吃
年龄为 54 岁的老李养了一只灰颜色的 3 的猫.
3 岁的灰颜色的猫眯着眼睛侧着头吃鱼

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(Dog dog, String something) {
System.out.println("年龄为 " + age + " 岁的" + name + "养了一只" + dog.getColor() + "颜色的 " + dog.getAge() + " 的狗.");
dog.eat(something);
}
public void keepPet(Cat cat, String something) {
System.out.println("年龄为 " + age + " 岁的" + name + "养了一只" + cat.getColor() + "颜色的 " + cat.getAge() + " 的猫.");
cat.eat(something);
}*/
// 想要一个方法, 能接受所有的动物, 包括猫和狗
// 方法的形参, 可以写这些类的父类 Animal
public void keepPet(Animal animal, String something) {
System.out.println("年龄为 " + age + " 岁的" + name + "养了一只" + animal.getColor() + "颜色的 " + animal.getAge() + " 的动物.");
animal.eat(something);
}
// 弊端: 1. 不能完美描述当前是猫还是狗, 只能笼统地称为动物. 2. 不能调用某一种动物的特有的方法.
}

进一步改进:

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(Dog dog, String something) {
System.out.println("年龄为 " + age + " 岁的" + name + "养了一只" + dog.getColor() + "颜色的 " + dog.getAge() + " 的狗.");
dog.eat(something);
}
public void keepPet(Cat cat, String something) {
System.out.println("年龄为 " + age + " 岁的" + name + "养了一只" + cat.getColor() + "颜色的 " + cat.getAge() + " 的猫.");
cat.eat(something);
}*/
// 想要一个方法, 能接受所有的动物, 包括猫和狗
// 方法的形参, 可以写这些类的父类 Animal
// public void keepPet(Animal animal, String something) {
// System.out.println("年龄为 " + age + " 岁的" + name + "养了一只" + animal.getColor() + "颜色的 " + animal.getAge() + " 的动物.");
// animal.eat(something);
// }
// 弊端: 1. 不能完美描述当前是猫还是狗, 只能笼统地称为动物. 2. 不能调用某一种动物的特有的方法.
// 进一步改进
public void keepPet(Animal animal, String something) {
if (animal instanceof Cat cat) {
System.out.println("年龄为 " + age + " 岁的" + name + "养了一只" + cat.getColor() + "颜色的 " + cat.getAge() + " 的猫.");
cat.eat(something);
} else if (animal instanceof Dog dog) {
System.out.println("年龄为 " + age + " 岁的" + name + "养了一只" + dog.getColor() + "颜色的 " + dog.getAge() + " 的狗.");
dog.eat(something);
} else {
System.out.println("没有这种动物.");
}
}
}
posted @   有空  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
点击右上角即可分享
微信分享提示