多态知识点多态的概念通俗同一件事不同对象去做产生不同结果例子动物都有eat()行为猫 eat 是吃鱼狗 eat 是吃骨头Java 实现多态的 3 个必要条件必须存在继承关系子类 extends 父类子类重写 (override) 父类的方法父类引用指向子类对象用父类引用调用重写后的方法补充多态的特点编译看父类运行看子类编译阶段编译器只认识父类类型运行阶段看引用实际指向的子类对象执行子类重写后的方法代码public class Animal { String name; int age; //构造方法 public Animal(String name,int age) { this.namename; this.ageage; } //父类方法 public void eat() { System.out.println(name吃饭); } }public class Cat extends Animal{ //由于父类写了代参构造那么子类也得构造一下父类的 public Cat(String name,int age) { super(name,age); } //重写父类方法 Override public void eat() { System.out.println(name吃鱼); } }public class Dog extends Animal{ public Dog(String name,int age) { super(name,age); } Override public void eat() { System.out.println(name啃骨头); } }//形参是父类Animal 父类引用可以接收任意子类对象多态核心 public static void eat(Animal a) { a.eat(); } public static void main(String[] args) { Cat catnew Cat(菊花猫,1); Dog dognew Dog(小黄,2); eat(cat); //传入Cat对象运行调用Cat中的eat eat(dog); //传入Dog对象运行调用Dog中的dog }调用者写eat(Animal a)方法时不需要关心传进来是猫还是狗只要是 Animal 子类都能传入运行自动匹配子类实现这就是多态带来的好处代码通用、易扩展重写方法重写Override / 覆盖定义子类重新实现父类的方法外壳方法名、参数不变只改内部方法体重写规则方法原型基本一致方法名、参数列表必须完全一样a返回值可以不一样但必须是父子类关系协变返回类型面试考点访问权限不能更严格a父类如果是public子类不能写成protected/private权限可以放宽例父类 protected子类可以 public不能重写的方法astatic静态方法、private私有方法、final方法、构造方法Override注解a显式标记这是重写编译器帮我们校验语法b如果方法名拼写错误比如把 eat 写成 aet直接编译报错防止写错重写 和 vs 重载总结a重载同一个类同名不同参数属于编译期多态b重写子类继承父类同名同参数属于运行时多态c方法重载是一个类的多态方法重写是子类与父类之间的多态public class Father { //方法重载 - 同一个类里方法名相同参数不同 public void say() { System.out.println(父亲说话无参数); } public void say(String world) { System.out.println(父亲说话world); } // 给子类继承的方法 public void work() { System.out.println(父亲的工作上班打工赚钱); } }public class Son extends Father{ //方法重写 -》 方法名参数列表完全相同 Override public void work() { System.out.println(儿子的工作上班敲代码); } }public static void main(String[] args) { Father fnew Father(); //调用 重载根据参数不同配备不同的方法 f.say(); f.say(我是Father); // 重写 Son sonnew Son(); son.work(); //多态的写法-- 父类引用指向子类对象 Father f1new Son(); f1.work(); //运行时 执行子类Son 里边的work 方法是动态绑定 Father f2new Father(); f2.work(); }重载Overload1同一个类2方法名一样3参数列表不一样4返回值、访问修饰符随便改5编译期就确定调用哪个方法 → 静态绑定6调用时看你传不传参数编译器直接选对应的方法重写Override1在父子两个类2方法名、参数列表完全一模一样3子类权限不能比父类更严格4运行时才确定调用哪个方法 → 动态绑定向上转型 和 向下转型向上转型自动类型转换定义子类对象当成父类对象来使用小范围 → 大范围自动转换语法父类类型 对象名 new 子类类型(); // 示例 Animal animal new Cat(元宝,2);使用场景1直接赋值子类对象直接赋值给父类引用2方法传参形参写父类类型可以接收任意子类对象3方法返回值返回值类型写父类可以返回任意子类对象特点1优点代码灵活统一编程多态的基础2缺陷父类引用只能调用父类的方法不能调用子类独有的方法如果子类重写了父类方法执行时会运行子类重写后的方法向下转型强制类型转换向上转型之后父类引用无法访问子类特有方法如果需要调用子类独有方法就要把父类引用强制转回子类类型就是向下转型语法子类类型 对象名 (子类类型)父类引用;向下转型不安全1如果父类引用实际指向的不是该子类对象编译不报错但运行抛出ClassCastException类型转换异常2Animal animal new Dog(); Cat cat (Cat) animal; // 运行报错animal本质是狗不能强转为猫安全方案instanceof1作用判断对象的真实类型是不是后面这个类返回布尔值2只有 instanceof 判断为 true才进行强制向下转型避免运行异常3if(animal instanceof Cat){ Cat cat (Cat) animal; cat.mew(); // 调用猫独有方法 } if(animal instanceof Dog){ Dog dog (Dog) animal; dog.bark(); // 调用狗独有方法 }常见面试题1向上转型什么时候发生直接赋值、方法传参、方法返回自动发生2向上转型后调用方法执行父类还是子类编译看左边父类有没有这个方法运行看右边对象真实是子类 执行子类重写方法3向下转型为什么要加 instanceof防止运行时ClassCastException类型转换异常总结1向上转型拿父类的壳装子类的东西只能看见父类的方法执行时跑子类重写方法2向下转型把壳拆回去变回子类才能调用子类独有的方法拆错了就崩所以先用instanceof判断代码public class Animal { private String name; private int age; //构造方法 public Animal(String name,int age) { this.namename; this.ageage; } 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 eat() { System.out.println(动物在吃东西); } public void sleep() { System.out.println(动物在睡觉); } }public class Dog extends Animal{ public Dog(String name,int age) { super(name,age); } //重写父类中的eat 方法 Override public void eat() { System.out.println(getName()小狗在啃骨头); } // 子类 Dog 中独有的方法 public void bark() { System.out.println(getName()汪汪); } }public class Cat extends Animal{ public Cat(String name,int age) { super(name,age); } //重写父类中的 eat方法 Override public void eat() { System.out.println(getName()猫在吃鱼); } //子类 Cat 中独有的方法 public void mew() { System.out.println(getName()喵喵); } }// 方法传参 - 形参为父类Animal ,可以接收任意子类对象 public static void eatFood(Animal a) { a.eat(); } //作为返回值- 返回任意子类对象 public static Animal buyAnimal(String var) { if(狗.equals(var)) { return new Dog(狗狗,1); } else if(猫.equals(var)) { return new Cat(猫猫,1); } else { return null; } }直接赋值子类对象赋值给父类引用向上转型向下转型补充Animal父类Cat、Dog两个子类都重写了eat()并且各自有独有的方法mew()/bark()向上转型Animal cat2 new Cat(元宝,2);1父类引用指向子类对象只能调用父类存在的方法执行时运行子类重写逻辑2无法直接调用子类独有方法向下转型想要调用mew()/bark()必须强制转回子类类型instanceof判断对象真实类型避免ClassCastException类型转换异常多态的有缺点多态public class Shape { //给子类继承的方法 public void draw() { System.out.println(图形); } }public class Cycle extends Shape { Override public void draw() { System.out.println(圆形); } }public class Rect extends Shape{ Override public void draw() { System.out.println(菱形); } }public class Flower extends Shape{ //重写父类方法 Override public void draw() { System.out.println(花); } }public class test { public static void drawShapes() { //父类数组存放子类对象多态 Shape[] shapes{new Cycle(),new Flower(),new Rect()}; for(Shape shape:shapes) { shape.draw(); } } public static void main(String[] args) { drawShapes(); } }Shape 是父类Rect、Cycle、Flower 是子类都重写了draw()方法父类引用指向子类对象就是多态多态的好处降低代码圈复杂度消除大量if-else1) 圈复杂度衡量代码复杂程度分支 / 循环越多越复杂项目规范一般要求圈复杂度≤10不使用多态大量 if-elsepublic static void drawShapes() { Rect rect new Rect(); Cycle cycle new Cycle(); Flower flower new Flower(); String[] shapes {cycle, rect, cycle, rect, flower}; for (String shape : shapes) { if (shape.equals(cycle)) { cycle.draw(); } else if (shape.equals(rect)) { rect.draw(); } else if (shape.equals(flower)) { flower.draw(); } } }新增图形时必须继续增加else if代码越来越臃肿使用多态1不需要任何 if 判断同一个方法draw()不同对象调用产生不同行为public static void drawShapes() { // 父类数组存放各个子类对象 Shape[] shapes {new Cycle(), new Rect(), new Cycle(), new Rect(), new Flower()}; for (Shape shape : shapes) { shape.draw(); // 自动调用对应子类重写的draw方法 } }可扩展性强新增类改动成本低1新增三角形Triangle只需要写一个继承Shape的类并重写draw()public class Triangle extends Shape{ Override public void draw() { System.out.println(三角形); } }2多态方式调用方drawShapes完全不用修改只需要往数组加new Triangle()3非多态 if-else 方式必须修改drawShapes新增 else if 分支4符合开闭原则对扩展开放对修改关闭多态的缺点父类引用不能直接访问子类独有的方法 / 属性需要强制类型转换运行时绑定相比静态编译会有微小性能开销现代 JVM 基本可忽略避免在构造方法中调用重写的方法结论1构造方法执行期间子类对象还没初始化完成2如果调用了可重写方法会触发动态绑定执行子类重写后的代码3子类方法里访问子类成员变量时变量还没赋值拿到的是默认值产生诡异 bug不要在构造方法中调用可以被子类重写的方法普通 public/protected 实例方法补充1子类对象构造先父构造再子类成员初始化2父构造里调用可重写方法会提前跑到子类重写方法此时子类变量还是默认值解决方法1构造方法里可以调用 private / final 方法因为这两类方法不能被子类重写不会触发动态绑定就不会有这个坑2private 方法不能重写无动态绑定aprivate方法只能在当前类内部使用子类不能重写b父类构造器中调用的func()永远是 B 自己的方法不会跑到子类 D 的 func不会出现变量未初始化问题3final 方法不能重写无动态绑定afinal修饰实例方法禁止子类 Override 重写b父类构造里调用 func一定执行 B 自己的 func不存在多态动态绑定不会调用子类代码c) 如果强行写 Override 重写 final 方法直接编译失败总结private私有子类根本看不见不能重写静态绑定final上锁不允许子类重写静态绑定public/protected普通方法可重写动态绑定父构造调用会执行子类重写方法此时子类成员还没初始化有坑