多态:polymorphism
概述
有了封装才有面向对象,有了面向对象才有继承和多态。
什么是多态:同类型的对象,表现出的不同形态。
多态的表现形式:父类类型 对象名称 = 子类对象;
多态的前提:
-
有继承或实现关系(实现与接口有关)
-
有父类引用指向子类对象,
Fu f = new Zi();
-
要有方法重写。
程序示例:
父类:
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() 方法。");
}
}
内存分析:
第一步,测试类的字节码文件进入方法区。
第二步,虚拟机自动调用 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(); // 业务逻辑发生改变时,后续代码无需修改
定义方法的时候,使用父类型作为参数,可以接收所有子多类对象,体现多态的扩展性与便利。
多态的弊端
不能调用子类特有的方法,即不存在于父类但是存在于子类的方法。因为用多态的方式定义的对象,在调用一个方法时,编译时看父类,运行时看子类,而父类中没有这个方法,则编译时报错。
解决方案:
利用类型的强制转换,将对象的类型由父类的类型转换为子类的类型。父类的范围比子类的范围大,就好像 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("没有这个类型,无法转换");
}
标签:name,show,多态,Dog,Animal,父类,public
From: https://www.cnblogs.com/Chengkai730/p/18403656