尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

第12章 多态

第12章 多态 第12章 多态面向对象三大特性之三多态Polymorphism。多态是一个引用类型多种形态是 Java 中最重要也最容易绕晕的概念。本节用例子把它讲透。先说明一下本系列按 JDK 8、JDK 11、JDK 17 三个长期支持版本讲解。多态的机制在三个版本下完全一致只有下文标注需 JDK 17 才能运行的 instanceof 模式匹配是 JDK 17 新增的写法JDK 8 / JDK 11 下用传统写法即可。一、什么是多态同一个行为不同对象有不同的表现形式。比如动物叫狗叫汪汪猫叫喵喵牛叫哞哞代码层面多态 父类引用指向子类对象AnimalanewDog();// 父类引用子类对象a.sound();// 汪汪类比一下你举着一张动物的牌子Animal 类型引用背后站的具体可能是狗、是猫——别人只看到动物这个身份但叫它的时候它用自己的方式回应。二、多态的三个前提要有继承关系或实现接口子类重写父类的方法父类引用指向子类对象classAnimal{voidsound(){System.out.println(动物叫);}}classDogextendsAnimal{Overridevoidsound(){System.out.println(汪汪);}}classCatextendsAnimal{Overridevoidsound(){System.out.println(喵喵);}}Animala1newDog();Animala2newCat();a1.sound();// 汪汪a2.sound();// 喵喵关键机制——动态绑定编译时看引用类型Animal运行时看实际对象类型Dog/Cat调用的是实际对象重写后的方法。三个前提缺一不可缺重写a.sound()输出的还是动物叫缺父类引用直接Dog d new Dog()只是普通调用。三、多态的好处面向抽象编程没有多态时要写很多 if 分支voidmakeSound(Animala){if(ainstanceofDog){((Dog)a).sound();}elseif(ainstanceofCat){((Cat)a).sound();}}有多态后代码大幅简化而且新增动物类型不需要改现有代码voidmakeSound(Animala){a.sound();// 不管传什么动物自动调用它的叫声}makeSound(newDog());// 汪汪makeSound(newCat());// 喵喵makeSound(newCow());// 哞哞新增 Cow 类即可makeSound 不用动这就是开闭原则对扩展开放、对修改关闭。多态让代码的可扩展性大大提升。再举一个图形绘制的例子感受新增类型零改动的威力classShape{voiddraw(){System.out.println(画一个图形);}}classCircleextendsShape{Overridevoiddraw(){System.out.println(画一个圆);}}classSquareextendsShape{Overridevoiddraw(){System.out.println(画一个正方形);}}把new Circle()、new Square()放进Shape[]循环调用s.draw()时各自画各自的输出画一个圆 / 画一个正方形 / 画一个圆。明天要支持三角形写个Triangle extends Shape重写 draw 就行调用方一行都不用改。四、多态中的转型向上转型自动DogdnewDog();Animalad;// 子类转父类自动完成安全子类对象总是是一个父类对象所以向上转型永远安全。向下转型强制父类引用想用子类特有方法需要向下转型AnimalanewDog();// a.watchDoor(); // 编译错误Animal 类型没有这个方法if(ainstanceofDog){// 先用 instanceof 判断类型Dogd(Dog)a;// 向下转型d.watchDoor();// 现在可以调用了}向下转型必须先判断如果a实际是 Cat强转成 Dog 会抛ClassCastException。instanceof就是用来避免这个异常的类型判断运算符。如果你用的是 JDK 17instanceof 判断加类型转换可以一步写成下面的样子instanceof 模式匹配// 需 JDK 17 才能运行if(ainstanceofDogd){// 判断通过后d 直接就是 Dog 类型d.watchDoor();}JDK 8 和 JDK 11 没有这个写法只能像前面那样先instanceof判断、再手动强转JDK 17 只是把两步合成一步逻辑完全一样。五、多态与成员变量、静态方法多态只对重写的方法生效对成员变量和静态方法无效classParent{Stringname父类;staticvoidwho(){System.out.println(父类静态方法);}}classChildextendsParent{Stringname子类;staticvoidwho(){System.out.println(子类静态方法);}}ParentpnewChild();System.out.println(p.name);// 父类变量看声明类型p.who();// 父类静态方法静态方法看声明类型成员变量看引用类型编译时类型不看实际对象静态方法属于类看引用类型实例方法非静态看实际对象类型——这才是多态一句话总结多态是实例方法的特权变量和静态方法不参与——它们在编译期就能确定归属不需要运行时动态绑定。六、多态的经典应用场景1. 方法参数用父类类型上面 makeSound 的例子方法参数类型写成父类可以接收所有子类对象。2. 返回值类型用父类类型AnimalcreateAnimal(Stringtype){if(dog.equals(type))returnnewDog();if(cat.equals(type))returnnewCat();returnnewAnimal();}3. 数组/集合装多态对象Animal[]zoonewAnimal[3];zoo[0]newDog();zoo[1]newCat();zoo[2]newDog();for(Animala:zoo){a.sound();// 每个元素调自己重写的方法}集合里同样通用ListAnimal可以同时装 Dog、Cat遍历时各自调各自的 sound()——List本身也是接口引用指向实现类对象ArrayList 实现了 List这就是多态思想。七、常见误区澄清误区 1Animal a new Dog()创建的是 Dog 还是 Animal答对象是 Dog实际类型引用是 Animal编译类型。对象本身在堆里就是 Dog只是通过 Animal 的视角看它——所以只能调用 Animal 里声明过的方法。误区 2多态没用直接Dog d new Dog()不就行了答单独一个对象确实看不出好处但当你有一堆不同类型的对象要统一处理时多态的价值就凸显了。八、扩展知识1. 向上转型与向下转型深入向上转型是收窄视角引用变宽Dog → Animal能调用的方法变少但永远安全。向下转型是恢复视角引用变窄Animal → Dog能调用的方法变多但有风险。关键要理解堆里的对象类型从 new 那一刻就定死了转型只是换了看它的方式没有改变对象本身。所以每次向下转型都带着一个疑问“这个引用背后真的是目标类型吗”——回答它就用 instanceof。2. 多态三条件与动态绑定的完整机制多态三条件再收紧一点继承或实现接口——建立父子关系重写——子类提供自己的实现父类/接口引用指向子类对象——让多种形态共用一个类型动态绑定Dynamic Binding是 Java 的运行时机制方法调用的目标方法在运行时根据对象实际类型决定。比如Animal a new Dog(); a.sound()——编译期编译器只检查 Animal 有没有 sound()运行期 JVM 看到 a 实际指向 Dog就调用 Dog 重写后的 sound()输出汪汪。与之相对的是静态绑定private 方法、static 方法、成员变量的调用目标在编译期就已确定所以它们不参与多态见第五节。3. 接口多态没有继承也能多态多态不只存在于类继承接口 实现类是更常见的多态形式interfaceFlyable{voidfly();}classBirdimplementsFlyable{Overridepublicvoidfly(){System.out.println(鸟在飞);}}classPlaneimplementsFlyable{Overridepublicvoidfly(){System.out.println(飞机在飞);}}Flyablef1newBird();// 接口引用指向实现类对象Flyablef2newPlane();f1.fly();// 鸟在飞f2.fly();// 飞机在飞鸟和飞机没有任何继承关系但都实现Flyable就能被统一当作会飞的处理——接口多态比继承多态更灵活一个类还能同时实现多个接口。下一章讲抽象类与接口时会重点展开。九、易错点1. 向下转型不判断直接强转AnimalanewCat();// 实际是 Cat// ❌ 不判断直接强转Dogd(Dog)a;// ❌ 运行时抛 ClassCastException// ✅ 先 instanceof 判断再转型if(ainstanceofDog){Dogd2(Dog)a;d2.watchDoor();}2. 父类引用调不到子类独有方法AnimalanewDog();// ❌ 编译错误Animal 类型里没有 watchDoor()编译期直接报错// a.watchDoor();// ✅ 确定是 Dog 后向下转型再调用if(ainstanceofDog){((Dog)a).watchDoor();}3. 以为静态方法也参与重写其实只是隐藏classParent{staticvoidwho(){System.out.println(父类静态方法);}}classChildextendsParent{staticvoidwho(){System.out.println(子类静态方法);}}ParentpnewChild();p.who();// 父类静态方法静态方法看声明类型与对象无关子类里写一个同名静态方法只是把父类的藏起来了叫隐藏/遮蔽不是重写也不会动态绑定。这也是为什么静态方法上不能加 Override。4. instanceof 判断顺序写反instanceof 有个特性子类对象对父类类型判断也是 trueDog 是 Animal 的实例。所以判断要从子到父否则父类判断会把子类对象截胡AnimalanewDog();// ❌ 先判断父类Dog 也满足 instanceof Animal子类分支永远走不到if(ainstanceofAnimal){// 这里只有 Animal 视角Dog 独有方法还得再转}elseif(ainstanceofDog){// 永远不执行((Dog)a).watchDoor();}// ✅ 先判断更具体的子类if(ainstanceofDog){((Dog)a).watchDoor();}elseif(ainstanceofAnimal){a.sound();}5. 忘了 instanceof 对 null 的安全行为Animalanull;// instanceof 对 null 一律返回 false不会抛异常System.out.println(ainstanceofAnimal);// false// ✅ a 可能是 null 时先 instanceof 判断再转型是安全的if(ainstanceofDog){// false直接跳过((Dog)a).watchDoor();}6. 以为成员变量也参与多态ParentpnewChild();System.out.println(p.name);// 父类变量看声明类型不看实际对象成员变量没有动态绑定看声明类型。想拿子类那份要么用子类引用要么显式转型后访问。十、小结多态 父类/接口引用指向子类对象 方法重写运行时看实际对象调用方法动态绑定编译期看引用类型向上转型自动安全向下转型必须先 instanceof 判断否则抛 ClassCastException多态只对实例方法生效成员变量、静态方法看声明类型多态实现面向抽象编程符合开闭原则让代码可扩展接口多态更灵活无继承关系的类实现同一接口也能统一处理JDK 17 的 instanceof 模式匹配可把判断转型合成一步需 JDK 17 运行下一节学习抽象类与接口。下一篇第13章 抽象类与接口
返回列表