🚀 一、多态的概念
通俗地讲:多态就是完成某个行为时,当对象不同时会发生不同的状态。
🌟 1.2 多态实现条件
在 Java 中要实现多态,必须要满足以下条件:
- 必须在继承的体系下。
- 子类必须要对父类中的方法进行重写。
- 通过父类的引用调用重写的方法。
多态体现:代码运行时,当传递不同对象时,会调用对应类中的方法。
class Animal{
String name;
int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(this.name + "在吃饭");
}
}
class Cat extends Animal{
public Cat(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(this.name + "吃鱼");
}
}
class Dog extends Animal{
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(this.name +"吃骨头");
}
}
// 分割线
public class Base{
// 编译器在编译代码时,并不知道调用的是哪个类中的方法
// 只有程序运行起来后,形参 a 引用的具体对象才确认,才知道调用哪个方法。
// 注意:此处形参类型必须是父类的类型才可以。
public static void eat(Animal a){
// 发生了动态绑定。
a.eat();
}
public static void main(String[] args) {
Cat c1 = new Cat("猫",2);
Dog d1 = new Dog("狗",1);
eat(c1);
eat(d1);
}
}
上面的代码中,分割线上方的代码是 类的实现者 编写的,分割线下方的代码是 类的调用者 编写的。
🌓
当类的调用者在编写 eat 这个方法时,参数类型是 Animal ,此时在方法内部不知道当前的 a 引用指向哪个类型的实例。此时 a 这个引用调用 eat 方法可能有多种不同的表现(和 a 引用的实例相关),这样的行为就叫多态。
🚀 二、重写
重写(override):也称为覆盖。重写是子类对父类非静态、非 private 修饰,非 final 修饰、非构造方法的实现过程进行重新编写,返回值和形参都不变。即外壳不变,核心重写。
重写的好处:子类可以根据需要,定义自己特定的行为。也就是子类可以根据需要实现父类的方法。
🌈方法重写的规则:
- 子类在重写父类方法时,一般必须与父类原型方法一致:修饰符、返回值类型、方法名(参数列表)要完全一致。
- 子类重写方法的访问权限不能比父类中原方法的更低。
- 父类被static、private修饰的方法和构造方法都不能被重写。
- 重写的方法,应该使用 @override 来注释。
🌈重写和重载的区别:
方法重载是一个类的多态性表现。而方法重写是子类和父类之间的多态性表现。
// 重载
class Dog{
// 方法名相同,参数列表不同
public void eat(){
System.out.println("");
}
@overload
public void eat(String name){
System.out.println("");
}
}
// 重写
class Dog{
public void bark(){
System.out.println("woof!");
}
}
class Hound extends Dog{
public void sniff(){
System.out.println("sniff");
}
// 重写了父类的 bark 方法,外壳相同,核心不同。
@override
public void bark(){
System.out.println("bowl!");
}
}
🌟 2.1 静态绑定和动态绑定
静态绑定:
在编译时,根据用户所传递的实参类型和个数就确定了具体调用的是哪个方法。典型代表是函数重载。
动态绑定:
编译时,并不能确定方法的具体行为,需要在程序运行时,才能够确定具体调用的是哪一个类的方法。
前提: 1. 父类引用子类的对象,即发生向上转型。2. 通过父类调用和子类同名的重写方法。
🚀 三、向上转型和向下转型
🌟 3.1 向上转型
创建一个子类对象,并当成父类对象使用。
父类类型 对象名 = new 子类类型();
Animal animal = new Dog();
使用场景:
- 直接赋值。
- 方法传参。
- 返回类型。
// 2. 方法传参,形参为父类类型的引用,可以接收任意的子类对象。
public static void eat(Animal a){
a.eat();
}
// 3. 做返回值类型,返回任意子类对象。
public static Animal buyAnimal(String a){
if(a == "dog"){
return new Dog();
}
else if(a == "cat"){
return new Cat();
}
else{
return null;
}
}
🔺 特点:
- 向上转型让代码更加灵活。
- 向上转型不能调用到子类特有的方法。
🌟 3.2 向下转型
子类在经过向上转型之后作为父类对象使用,无法再调用子类的方法,但是要想调用子类特有的方法,只需向下转型即可。
// 向上转型一个 Animal 类
Animal animal = new Cat();
// 强制向下转型
cat = (Cat)animal;
cat.eat();
向下转型并不安全,如果转换失败,运行时会出现异常。Java 中为提高向下转型的安全性,引入了 instanceof ,如果表达式为 true 则安全转换。
public static void main(String[] args) {
Animal animal = new Cat();
// 哪个安全,animal 就会向下转型为哪个类型。
if(animal instanceof Cat){
Cat cat = (Cat)animal;
cat.eat();
}
if(animal instanceof Dog){
Dog dog = (Dog)animal;
dog.eat();
}
}
🚀 四、多态的优缺点
优点:
- 降低代码的 “圈复杂度” ,避免使用大量的 if - else 。
- 可拓展能力强。
缺陷:
代码的运行效率低。
避免在构造方法中调用重写的方法。
用尽量简单的方式使对象进入可工作状态。尽量不在构造器中调用方法,如果这个方法被子类重写,就会触发动态绑定,但是此时子类的构造还未完成。可能会出现隐藏的极难发现的问题。