1. 前言
现实中的多态:同一个对象在不同时刻展现出的不同形态!
编程中的多态:
对于具体的类型和调用的方法,在编程时并不确定,而是在程序运行期间才确定,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
核心:允许在编译时使用父类类型,而在运行时使用子类类型的实现。
调用成员变量:编译看左边,运行看左边
- 编译看左边:左边(父类)中存在该变量编译器不会出现编译时异常
- 运行看右边:实际输出的结果是父类的内容
扩展:对于继承中,变量查找顺序,在堆中创建内存的时候,需要有一块存储父类的变量,查找的时候先找父类的,再找子类的。
调用成员方法:编译看左边,运行看右边
- 编译看左边:左边(父类)中存在该变量编译器不会出现编译时异常
- 运行看右边:实际输出的结果是子类重写的内容
多态的作用:消除类型之间的耦合关系。
概述一下上述:实现动态绑定,在执行期间判断所引用对象的实际类型,根据实际的类型调用其相应的方法。
2. 条件和格式
Java 实现多态有 3 个必要条件:继承、重写和向上转型。
格式:父类 变量名 = new 子类();
- 必须在继承体系之下
前者为后者的父类
- 子类必须对父类当中的方法进行重写
多态性允许不同对象对同一消息做出不同的响应。通过重写父类的方法,子类可以根据自身的特性提供不同的实现。当使用父类引用指向子类对象时,调用重写的方法会调用子类的实现,从而实现多态。
- 必须通过父类的引用调用重写的方法(向上转型)
可以在编译时,使用范围更大的数据类型。
当使用父类的引用调用方法时,调用方只关心父类定义的接口,而不需要知道具体的子类实现细节。这种设计有助于减少代码间的依赖性。
下面我将通过两个方面介绍:多态的好处
2. 编译时多态
编译时多态就是我们平常所熟知的重载,也就是方法的参数列表(类型、个数、顺序有一项不同即可)不同。这里其实是广义上的多态的概念,那么从狭义上的多态来理解的话,我们所说的多态其实就是运行时多态,也就是再编译阶段,我们无法确定对象的类型,只有在运行的时候我们才能确定类型,并且调用相应类型的方法。
好处:可以在编译时,使用范围更大的数据类型。
2.1. 使用父类的类型存储子类
public class Test {public static void main(String[] args) {//创建父类数组(更广泛的数据类型)Person[] p = new Person[5];p[0] = new Student();//存进去会进行自动类型转换(向上转型)p[1] = new Teacher();}
}
如果你只使用特定的子类(小的数据范围),绝对无法存储。
举例:我创建一个人可以进入的房间,老师和学生都属于人自然都可以进入。但是我要设置只有老师可以进入,学生自然不能进入。
2.2 参数传参(方法重载)
public class Test2 {public static void main(String[] args) {Student s = new Student();Teacher t = new Teacher();show(s);show(t);}private static void show(Person p){System.out.println(p.getName() + "查看了");}
}
上述方法与下述方法等价:
private static void show(Student s){System.out.println(s.getName() + "查看了");}private static void show(Teacher t){System.out.println(t.getName() + "查看了");}
这样看不出是否方便,但是person如果有一百个子类呢,哪个方便就可想而知了。
举例:一个学生管理平台,有一个登录方法。如果我设置只有老师可以登录(调用登录方法),
学生不乐意了,如果这样学生,老师不乐意了,所以为了灵活性和可扩展性,我可以设置这样是人都可以调用这个方法,所以二者都可以调用登录方法。
public boolean login(Person p) {}
2.3 方法返回值
// 返回一个随机动物的方法public static Animal getRandomAnimal() {Random random = new Random();if (random.nextBoolean()) {return new Dog(); // 向上转型为 Animal} else {return new Cat(); // 向上转型为 Animal}}
这跟上述一样,要使用更大的范围接受返回值。
3. 运行时多态
运行时多态是指在程序运行时,调用同一个方法名但具体执行的代码可能会根据调用对象的实际类型而有所不同的现象。这是通过Java中的方法重写(Override)和动态绑定(Dynamic Binding)机制实现的。
这就是核心:允许在编译时使用父类类型,而在运行时使用子类类型的实现。
// 动物类
class Animal {public void makeSound() {System.out.println("动物发出了声音");}
}// 狗类,继承自动物类
class Dog extends Animal {@Overridepublic void makeSound() {System.out.println("汪汪汪~");}
}// 猫类,继承自动物类
class Cat extends Animal {@Overridepublic void makeSound() {System.out.println("喵喵喵~");}
}public class Main {public static void main(String[] args) {Animal animal1 = new Dog(); // 向上转型Animal animal2 = new Cat(); // 向上转型animal1.makeSound(); // 运行时调用的是 Dog 类的 makeSound 方法animal2.makeSound(); // 运行时调用的是 Cat 类的 makeSound 方法}
}
在这个示例中:
animal1
和animal2
都被声明为Animal
类型,但实际上它们分别指向Dog
和Cat
类型的对象。- 当调用
animal1.makeSound()
和animal2.makeSound()
时,由于动态绑定,实际调用的是Dog
类和Cat
类中重写的makeSound()
方法。 - 这里的关键是,在编写
main
方法时,并不需要知道animal1
和animal2
分别是Dog
和Cat
类的对象,只需知道它们是Animal
类型的对象即可。
4. 多态的弊端
多态的弊端:不可以访问子类特有的方法。
即便是向上转型,将子类的引用赋值给父类对象,那么这个父类对象也只能访问子类从父类当中继承来的方法,不可以访问子类当中特有的方法,这是为什么呢?
因为我们多态的实现,是在编译期间动态绑定实现的,也就是说只有在运行期间才能够确定,父类所引用的子类类型是什么,在编译期间我们只知道,父类的类型是什么,所以这也就导致了,向上转型之后,父类对象也只能访问子类当中从父类继承来的方法,不能够访问子类特有的方法。因为编译器知道父类的类型并不知道,你到底使用的哪个子类,子类的类型需要运行的时候才能知道。不能随便调用,如果传来的是猫,给你调用狗的方法就尴尬了。
解决问题的方案:
针对这个问题,我们有提出了向下转型,这样就可以使得我们父类引用方便地访问我们子类当中特有的方法。
向下转型:
把高类型的引用转换成低类型的引用
格式: 低类型 变量 = (低类型)高类型的变量;
package Yangon;public class Animal {public int age;public String name;public void eat() {System.out.println("吃东西");}
}
class Dog extends Animal{public void eat(){System.out.println("小狗正在吃东西");}public void play(){System.out.println("小狗正在玩耍");}
}
class Bird extends Animal{public void eat(){System.out.println("小鸟正在吃东西");}
}
class Main{public static void main(String[] args) {Animal animal = new Dog();if(animal instanceof Dog){Dog myRealDog = (Dog)animal;myRealDog.play();//这里是可以调用子类的特有方法的,因为对父类进行了强制类型转换//也就是向下转型}Animal animal1_bird= new Bird();if(animal1_bird instanceof Bird){//编译就会报错,不允许将Bird 赋值给 Dog类,这就是instanceof的意义所在Dog bird_dog = (Bird)animal1_bird;}}
}
对于上述代码使用了,instanceof,下面我将讲解instanceof的作用
5. instanceof
instanceof 是 Java 中的一个关键字,用于检查一个对象是否是一个特定类的实例或者是其子类的实例。其主要作用是进行类型检查,可以用来确定一个对象的实际类型是否与我们期望的类型兼容。
语法格式: object instanceof Class
其中 Object 是要检查的对象,Class 是要检查的类或接口。
作用:
1. 类型检查:
instanceof
主要用于判断一个对象是否属于某个类的实例,或者是否实现了某个接口。例如:
class Animal {}
class Dog extends Animal {}Animal animal = new Dog();if (animal instanceof Dog) {System.out.println("animal is a Dog");
}
- 在上述例子中,
animal instanceof Dog
返回 true,因为animal
是Dog
类的实例。
2. 避免类型转换异常:
if (animal instanceof Dog) {Dog dog = (Dog) animal; // 安全地进行类型转换dog.bark();
}
3. 多态性的应用:
instanceof
在处理多态情况下尤为重要。它允许程序在运行时动态地确定对象的实际类型,从而根据不同类型采取不同的行为。
4. 判断对象是否为 null:
- 使用
instanceof
还可以避免对 null 对象进行操作而导致空指针异常。例如:
if (animal instanceof Dog) {// 对 animal 进行操作,因为 animal 不是 null 且是 Dog 的实例
} else {// animal 是 null 或者不是 Dog 的实例
}
总之,instanceof
是 Java 中用于进行类型检查的重要工具,能够帮助开发人员在运行时确定对象的实际类型,从而进行安全的类型转换或者采取适当的行动。