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

资讯详情

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

第九集,多态的理解

第九集,多态的理解 目录一.多态基础二.重写三.向上向下转型四.多态的优缺点五.foreach语句六.instanceof七.总结与重点提醒一.多态基础1.认识多态多态Polymorphism简单理解就是一个对象可以表现出多种状态但仅仅这样理解是不够的。多态的核心价值在于在继承体系下子类可以重写父类的方法从而让同一个方法调用在不同子类中表现出不同的行为实现“同一接口多种实现”的灵活性。多态重写、向上转型、动态绑定。实现多态需要满足以下三个条件有继承关系子类继承父类形成父子层级。子类重写父类的方法子类对父类的方法进行覆盖提供自己的实现。通过父类的引用调用方法使用父类类型的引用指向子类对象调用被重写的方法时实际执行的是子类的实现。2.动态绑定原理多态之所以能实现关键在于 JVM 在运行时根据对象的实际类型来决定调用哪个方法这一机制被称为动态绑定Dynamic Binding也叫晚期绑定Late Binding。与之相对的是编译期就能确定的静态绑定Static Binding例如重载方法、private 方法、static 方法等它们在编译阶段就已经确定了调用目标。我自己理解的动态绑定首先子类对象发生了向上转型即把子类对象赋值给父类类型的引用。Animal animal new Dog(小黑, 2);这里虽然 Dog 对象向上转型了但 animal 这个引用只能调用父类中声明的方法子类重写的方法除外不能通过父类引用去调用子类中独有的方法。然后自己编写一个接收 Animal 类型参数的成员方法这里的形参 animal 就充当父类引用。public static void useAnimal(Animal animal){ animal.eat(); }再在 main 方法中调用 useAnimal 这个方法。useAnimal(dog); useAnimal(cat);你看这里把 dog 传进去后它就替代了形参 animal所以方法内部调用的是 dog 重写后的 eat() 方法cat 参数也是同样的道理你传入哪个对象就调用哪个对象特有的实现。比如动物都会吃饭但狗吃狗粮猫吃猫粮。二.重写1.重写的条件重写Override需要满足以下条件方法名一致子类重写的方法名必须与父类被重写的方法名相同。参数列表一致参数的类型、顺序、个数都必须与父类方法保持一致。返回值一致返回值类型必须与父类方法相同或为其子类型。注意被 private、static、final 修饰的方法不能被重写。注意不要在父类的构造方法中调用父类和子类同名的方法。2.重写和重载的相同点与区别相同点重写和重载的方法名都必须与所在类中的方法名一致。重写Override发生在子类与父类之间要求方法名、参数类型、个数和顺序都与父类方法完全一致。其目的是在继承父类方法的基础上让子类提供自己的实现从而体现方法行为的多样性即“多种状态”。重载Overload发生在同一个类中要求方法名相同但参数的类型、个数或顺序中至少有一项不同。其目的是让同名方法根据传入参数的不同实现不同的功能。关键区别重写必须建立在继承关系之上是子类对父类方法的覆盖而重载不需要继承关系在同一个类中即可实现是方法名的复用。对比项重写Override重载Overload发生位置子类与父类之间同一个类中方法名必须与父类方法一致必须与同类其他方法一致参数列表类型、个数、顺序必须完全一致类型、个数或顺序至少有一项不同返回值必须与父类方法相同或为其子类型无要求可以不同继承关系必须建立在继承关系之上不需要继承关系目的在继承父类方法的基础上让子类提供自己的实现体现方法行为的多样性即“多种状态”让同名方法根据传入参数的不同实现不同的功能三.向上向下转型在 Java 中转型Casting是指将一种类型的引用转换为另一种类型的引用。在多态体系中转型主要分为向上转型Upcasting和向下转型Downcasting两种它们分别对应着不同的使用场景和注意事项。1. 向上转型Upcasting定义向上转型是指将子类对象的引用赋值给父类类型的引用即把子类类型转换为父类类型。这种转型是自动完成的不需要显式声明因此也被称为隐式转型。语法示例// 父类 class Animal { public void eat() { System.out.println(动物在吃东西); } } // 子类 class Dog extends Animal { Override public void eat() { System.out.println(狗在啃骨头); } public void bark() { System.out.println(狗在汪汪叫); } } public class UpcastingDemo { public static void main(String[] args) { // 向上转型子类对象赋值给父类引用 Animal animal new Dog(); animal.eat(); // 输出狗在啃骨头动态绑定调用子类重写的方法 // 注意此时无法调用子类特有的方法 // animal.bark(); // 编译错误Animal 类型中没有 bark() 方法 } }注意事项向上转型是安全的因为子类一定拥有父类的所有成员。通过父类引用调用方法时如果该方法被子类重写则实际执行的是子类的实现动态绑定。向上转型后父类引用无法访问子类特有的属性和方法。2. 向下转型Downcasting定义向下转型是指将父类类型的引用强制转换为子类类型的引用即把父类类型转换为子类类型。这种转型需要显式声明因此也被称为强制转型。语法示例public class DowncastingDemo { public static void main(String[] args) { // 先向上转型 Animal animal new Dog(); // 向下转型需要显式强制转换 Dog dog (Dog) animal; dog.bark(); // 输出狗在汪汪叫 // 此时可以访问子类特有的方法 dog.eat(); // 输出狗在啃骨头 } }注意事项向下转型不是自动完成的必须使用强制类型转换语法。向下转型存在风险如果父类引用实际指向的不是目标子类对象运行时将抛出ClassCastException异常。为了安全向下转型前应使用instanceof关键字进行检查。3. 使用 instanceof 安全转型在进行向下转型之前推荐先使用instanceof判断对象的真实类型以避免运行时异常。public class SafeDowncastingDemo { public static void main(String[] args) { Animal animal new Dog(); // 使用 instanceof 检查后再转型 if (animal instanceof Dog) { Dog dog (Dog) animal; dog.bark(); // 输出狗在汪汪叫 } else { System.out.println(该对象不是 Dog 类型无法转型); } } }3. 向上转型与向下转型对比为了更直观地理解向上转型和向下转型的区别下面通过一个对比表格进行总结。对比项向上转型Upcasting向下转型Downcasting定义将子类对象的引用赋值给父类类型的引用即把子类类型转换为父类类型将父类类型的引用强制转换为子类类型的引用即把父类类型转换为子类类型转换方式自动完成无需显式声明属于隐式转型必须显式声明使用强制类型转换语法属于强制转型安全性安全因为子类一定拥有父类的所有成员存在风险若父类引用实际指向的不是目标子类对象运行时将抛出ClassCastException异常使用场景作为多态的基础通过父类引用统一操作不同子类对象实现代码的通用性和灵活性在需要访问子类特有的属性和方法时将父类引用恢复为具体的子类类型注意事项转型后父类引用无法访问子类特有的属性和方法调用被重写的方法时实际执行的是子类的实现动态绑定转型前应使用instanceof检查对象的真实类型避免直接强制转换导致运行时异常4. 完整示例演示转型过程下面通过一个完整的类继承示例演示向上转型和向下转型的完整过程。// 父类 class Animal { public void eat() { System.out.println(动物在吃东西); } } // 子类 class Dog extends Animal { Override public void eat() { System.out.println(狗在啃骨头); } public void bark() { System.out.println(狗在汪汪叫); } } // 子类 class Cat extends Animal { Override public void eat() { System.out.println(猫在吃鱼); } public void meow() { System.out.println(猫在喵喵叫); } } public class CastingDemo { public static void main(String[] args) { // 向上转型子类对象赋值给父类引用 Animal animal1 new Dog(); Animal animal2 new Cat(); // 通过父类引用调用重写方法动态绑定 animal1.eat(); // 输出狗在啃骨头 animal2.eat(); // 输出猫在吃鱼 // 向下转型使用 instanceof 检查后安全转型 if (animal1 instanceof Dog) { Dog dog (Dog) animal1; dog.bark(); // 输出狗在汪汪叫 } if (animal2 instanceof Cat) { Cat cat (Cat) animal2; cat.meow(); // 输出猫在喵喵叫 } // 错误的向下转型示例会抛出异常 // Cat cat (Cat) animal1; // 运行时抛出 ClassCastException } }运行结果狗在啃骨头 猫在吃鱼 狗在汪汪叫 猫在喵喵叫小结向上转型是多态的基础它让代码更加灵活和通用向下转型则用于在需要访问子类特有功能时恢复具体类型。向下转型必须配合instanceof检查才能保证程序的健壮性。四.多态的优缺点多态是面向对象编程的三大特性之一它在带来强大灵活性的同时也伴随着一定的代价。下面分别从优点和缺点两个角度进行分析。1. 多态的优点提高代码的可扩展性新增子类时无需修改调用方的代码只需让新子类继承父类并重写方法即可被现有逻辑直接使用符合开闭原则。提高代码的可维护性通过父类引用统一操作不同子类对象使代码结构清晰、逻辑集中减少重复的条件判断便于阅读和维护。提高代码的灵活性同一个方法调用可以根据对象的实际类型表现出不同的行为让程序在运行时动态决定执行逻辑从而更灵活地应对需求变化。降低模块间的耦合度调用方只依赖父类抽象而不依赖具体子类使模块之间的依赖关系更松散便于独立开发和测试。2. 多态的缺点性能开销多态依赖动态绑定方法调用需要在运行时查找实际类型并确定执行的方法相比直接调用会有一定的性能损耗。类型转换风险向下转型时如果父类引用实际指向的不是目标子类对象运行时将抛出ClassCastException异常需要借助instanceof检查来规避。增加理解难度多态让代码的执行行为不再直观阅读代码时无法仅凭调用语句判断实际执行的是哪个类的方法增加了调试和理解的成本。限制子类特有功能通过父类引用调用方法时无法直接访问子类特有的属性和方法若需使用必须进行向下转型反而增加了代码的复杂度。3. 实际场景示例下面以「动物园喂食系统」为例演示多态带来的好处。系统需要为不同动物提供不同的食物如果不使用多态每新增一种动物都要修改喂食逻辑代码会越来越臃肿。// 父类 class Animal { public void eat() { System.out.println(动物在吃东西); } } // 子类 class Dog extends Animal { Override public void eat() { System.out.println(狗在啃骨头); } } // 子类 class Cat extends Animal { Override public void eat() { System.out.println(猫在吃鱼); } } // 子类 class Panda extends Animal { Override public void eat() { System.out.println(熊猫在吃竹子); } } public class ZooFeedingDemo { // 喂食方法只依赖父类引用无需关心具体动物类型 public static void feed(Animal animal) { animal.eat(); } public static void main(String[] args) { Animal dog new Dog(); Animal cat new Cat(); Animal panda new Panda(); // 同一个 feed 方法对不同对象表现出不同行为 feed(dog); // 输出狗在啃骨头 feed(cat); // 输出猫在吃鱼 feed(panda); // 输出熊猫在吃竹子 } }运行结果狗在啃骨头 猫在吃鱼 熊猫在吃竹子好处分析可扩展性新增动物如熊猫时只需新增一个继承Animal的子类并重写eat()方法feed()方法完全不需要修改。可维护性喂食逻辑集中在一个feed()方法中避免了使用大量if-else或switch判断动物类型代码更加简洁清晰。灵活性调用方只依赖父类抽象程序在运行时根据对象的实际类型自动选择执行对应子类的eat()实现。4. 小结多态的核心价值在于「面向抽象编程」它让代码具备良好的可扩展性、可维护性和灵活性是设计模式与大型项目架构的重要基础。但在追求灵活性的同时也要注意动态绑定带来的性能开销和向下转型的类型安全风险。在实际开发中应优先通过父类引用操作对象确需访问子类特有功能时务必使用instanceof检查后再进行向下转型。五.foreach语句在这里我单独解释这个语句因为后面在多态中会用到。基本语法for(元素类型 变量名 : 数组或集合){ 循环体 }其中变量名可以自行定义。foreach 是 Java 5 之后提供的增强型 for 循环用于简化对数组和集合的遍历它会在底层自动完成迭代器的创建和调用无需手动维护下标或调用next()方法。与普通 for 循环的区别普通 for 循环通过下标访问元素适合需要修改元素或获取索引的场景而 foreach 直接取出每个元素代码更简洁但无法在遍历过程中修改数组或集合的结构如增删元素否则会抛出ConcurrentModificationException异常。在多态中的应用当数组或集合中存放的是父类引用时foreach 遍历出的变量类型就是父类类型。此时通过该变量调用方法会依据动态绑定机制实际执行每个对象对应子类重写后的方法。如果某个子类有自己独有的方法则需要配合instanceof判断后向下转型才能调用。小结foreach 让数组和集合的遍历更加简洁在多态场景下它配合动态绑定可以统一处理不同类型的子类对象当需要访问子类特有方法时再结合instanceof安全向下转型即可。六.instanceof语法对象 instanceof 类型返回一个布尔值true或false用于判断某个对象是否属于指定类型或其子类型。作用在多态体系中父类引用可能指向任意一个子类对象。通过instanceof可以在运行时判断对象的真实类型从而决定是否可以进行向下转型避免直接强制转换导致ClassCastException异常。使用场景向下转型前安全检查在强制转换之前先用instanceof判断对象是否为目标类型确保转型安全。多类型分支处理当父类引用可能指向多种子类对象时通过instanceof区分不同子类分别调用各自特有的方法。集合中对象的类型判断遍历存放父类引用的数组或集合时用instanceof判断每个元素的实际类型再执行对应的操作。小结instanceof是向下转型的“安全锁”在强制转换前先判断对象的真实类型能有效避免ClassCastException异常让多态代码更加健壮可靠。七.总结与重点提醒本文围绕 Java 多态展开从定义、实现条件、底层原理到重写与重载、向上向下转型、优缺点、foreach 与 instanceof 等知识点做了系统梳理。最后把学习与使用多态时最需要留意的重点总结如下多态的三个实现条件缺一不可必须有继承关系、子类必须重写父类方法、必须通过父类引用调用方法。三者共同作用才能让同一个方法调用在不同子类中表现出不同行为。动态绑定是多态的核心JVM 在运行时根据对象的实际类型决定调用哪个方法而不是根据引用类型。因此通过父类引用调用被重写的方法时实际执行的一定是子类的实现。重写与重载不要混淆重写发生在子类与父类之间方法名、参数列表、返回值都必须一致重载发生在同一个类中参数列表必须不同。重写依赖继承关系重载不依赖。向上转型安全向下转型有风险向上转型自动完成且安全向下转型必须显式强制转换若父类引用实际指向的不是目标子类对象会抛出ClassCastException异常。向下转型前务必使用 instanceof 检查这是避免类型转换异常最有效的手段也是多态代码健壮性的重要保障。被 private、static、final 修饰的方法不能被重写这些方法在编译期就已确定调用目标不参与动态绑定。foreach 遍历父类引用时注意类型遍历出的变量是父类类型调用方法时依据动态绑定执行子类实现如需访问子类特有方法要配合 instanceof 安全向下转型。优先面向抽象编程在实际开发中应尽量通过父类引用操作对象减少对具体子类的依赖从而提升代码的可扩展性、可维护性和灵活性。一句话总结多态让代码更灵活、更通用但它的正确使用离不开对动态绑定、重写规则和类型转换安全性的深刻理解。掌握这些重点你就能写出既优雅又健壮的多态代码。
返回列表