Java多态六重奏:从升天到下凡的仙人之旅各位道友,贫道会编程的吕洞宾又来传道了!今天咱们要一口气讲清楚Java多态的六大法宝:向上转型、向下转型、转机、构造器与多态、协变返回类型、替代与扩展。准备好你的飞剑(IDE),咱们御剑飞行! 第一章:向上转型——仙人升天,身份提升向上转型(Upcasting)就是子类对象被当作父类使用,这是多态的基石。 升天的正确姿势// 基类:仙人class Immortal { void cultivate() { System.out.println("Immortal cultivation"); }}// 派生类:剑仙class SwordImmortal extends Immortal { @Override void cultivate() { System.out.println("Sword Immortal practicing sword"); } void flyWithSword() { System.out.println("Flying with sword"); }}public class UpcastingDemo { public static void main(String[] args) { // 向上转型:自动、安全 Immortal immortal = new SwordImmortal(); immortal.cultivate(); // 输出: Sword Immortal practicing sword // immortal.flyWithSword(); // 编译错误!父类看不到子类独门技能 }}
向上转型的特点(逐条说明)- 自动安全转型
- 编译器自动处理,无需显式转换
- 百分百安全,因为剑仙肯定是仙人
- 如知识库所言:"从派生类转型为基类是向上的,所以通常称作向上转型"
- 接口可能缩小
- 转型后只能调用父类声明的方法
- 子类特有的方法被隐藏
- 如知识库例子:Wind转型为Instrument后,无法调用Wind特有方法
- 实际对象不变
- 对象还是那个子类对象
- 只是引用类型变了
- 如知识库所说:"Wind对象同时也是一个Instrument对象"
向上转型的好处看看知识库的音乐例子: class Instrument { public void play(Note n) { System.out.println("Instrument.play()"); }}class Wind extends Instrument { @Override public void play(Note n) { System.out.println("Wind.play() " + n); }}public class Music { // 一个方法处理所有乐器 public static void tune(Instrument i) { i.play(Note.MIDDLE_C); } public static void main(String[] args) { Wind flute = new Wind(); tune(flute); // 向上转型 }}
优点: - 代码复用:一个tune()方法搞定所有乐器
- 扩展性强:新增乐器类型不改动tune()
- 解耦合:不依赖具体实现
第二章:向下转型——仙人下凡,小心摔跤向下转型(Downcasting)是把父类引用转回具体的子类,这是有风险的! 下凡的危险class Immortal { void cultivate() { System.out.println("Immortal cultivation"); }}class SwordImmortal extends Immortal { void flyWithSword() { System.out.println("Flying with sword"); }}class TalismanImmortal extends Immortal { void useTalisman() { System.out.println("Using talisman"); }}public class DowncastingDemo { public static void main(String[] args) { Immortal immortal = new SwordImmortal(); // 正确的向下转型 if (immortal instanceof SwordImmortal) { SwordImmortal swordImmortal = (SwordImmortal) immortal; swordImmortal.flyWithSword(); } // 危险的错误转型(运行时异常) Immortal another = new TalismanImmortal(); try { SwordImmortal wrong = (SwordImmortal) another; // ClassCastException! wrong.flyWithSword(); } catch (ClassCastException e) { System.out.println("摔跤了!类型转换错误"); } }}
向下转型的特点- 需要显式转换
- 必须用强制类型转换:(SwordImmortal) immortal
- 编译器不会自动完成
- 运行时检查
- Java在运行时检查类型
- 错误转型抛出ClassCastException
- 如知识库所说:"每次转型都会被检查!"
- 需要使用instanceof
- 安全做法:先检查再转型
- 避免运行时异常
知识库中的向下转型例子class Useful { void f() { System.out.println("Useful.f()"); } void g() { System.out.println("Useful.g()"); }}class MoreUseful extends Useful { void u() { System.out.println("MoreUseful.u()"); } void v() { System.out.println("MoreUseful.v()"); }}public class RTTI { public static void main(String[] args) { Useful[] x = { new Useful(), new MoreUseful() }; // 需要向下转型才能调用扩展方法 ((MoreUseful) x[1](@ref).u(); // 正确的向下转型 // 错误的向下转型会抛出异常 // ((MoreUseful) x[0](@ref).u(); // ClassCastException! }}
第三章:转机——多态的灵魂,后期绑定转机就是方法调用在运行时确定,这是多态能工作的核心! 早期绑定 vs 后期绑定// 早期绑定:编译时确定class EarlyBinding { void method() { System.out.println("Early binding"); }}// 后期绑定:运行时确定(多态)class Base { void method() { System.out.println("Base method"); }}class Derived extends Base { @Override void method() { System.out.println("Derived method"); }}public class BindingDemo { public static void callMethod(Base obj) { obj.method(); // 编译时不知道调用哪个 } public static void main(String[] args) { Base obj = new Derived(); callMethod(obj); // 输出: Derived method }}
Java的动态绑定机制- 默认动态绑定
- Java所有非static、非private、非final方法都是动态绑定的
- 如知识库所说:"在Java中,动态绑定是默认行为"
- 虚拟方法表(vtable)
- 每个类维护一个方法表
- 运行时根据实际对象类型查找方法
- 与传统语言的对比
- C++需要virtual关键字
- Java默认就是动态的
为什么需要转机?看反例// 反例:没有多态,需要一堆方法class Music2 { public static void tune(Wind i) { i.play(); } public static void tune(Stringed i) { i.play(); } public static void tune(Brass i) { i.play(); } // 每新增一种乐器,就要加一个tune方法!}// 正确:利用多态class Music { public static void tune(Instrument i) { i.play(); // 一个方法搞定 }}
反例问题: - 代码冗余:重复代码多
- 维护困难:新增类型要改多处
- 容易出错:忘记重载不报错
第四章:构造器与多态——先筑基还是先修炼?在构造器中调用多态方法是个大坑! 构造器的调用顺序class Foundation { Foundation() { System.out.println("Foundation constructor"); cultivate(); // 危险!在构造中调用多态方法 } void cultivate() { System.out.println("Foundation cultivation"); }}class AdvancedCultivation extends Foundation { private int level = 9; // 九重天 @Override void cultivate() { System.out.println("Advanced cultivation level: " + level); } public static void main(String[] args) { new AdvancedCultivation(); // 输出: // Foundation constructor // Advanced cultivation level: 0 ← 注意!不是9! }}
为什么level是0而不是9?看知识库的说明: - 构造顺序:
- 分配内存,变量设默认值(level=0)
- 调用基类构造器
- 初始化成员(level=9)
- 执行派生类构造器
- 多态方法的陷阱
- 基类构造器调用cultivate()时,对象已经是AdvancedCultivation类型
- 但level还没初始化(还是0)
构造器最佳实践// 反例:构造器中做太多class BadDesign { private Database db; private Cache cache; BadDesign() { db = new Database(); cache = new Cache(); initialize(); // 调用多态方法 loadData(); // 太复杂! } void initialize() { // 可能被子类重写 }}// 正确:构造器只做基本初始化class GoodDesign { private Database db; private Cache cache; GoodDesign() { db = new Database(); cache = new Cache(); // 只做最基本的 } // 专门的方法初始化 final void initialize() { // final防止子类修改 // 基础初始化 } void setup() { initialize(); loadData(); }}
第五章:协变返回类型——子类可以更具体Java 5引入协变返回类型,让子类重写方法时可以返回更具体的类型。 协变返回的例子class ImmortalPill { @Override public String toString() { return "普通仙丹"; }}class GoldenPill extends ImmortalPill { @Override public String toString() { return "九转金丹"; }}// 丹炉基类class PillFurnace { ImmortalPill makePill() { return new ImmortalPill(); }}// 金丹炉class GoldenPillFurnace extends PillFurnace { @Override GoldenPill makePill() { // 协变返回:返回更具体的GoldenPill return new GoldenPill(); }}public class CovariantReturnDemo { public static void main(String[] args) { PillFurnace furnace = new PillFurnace(); ImmortalPill pill1 = furnace.makePill(); System.out.println(pill1); // 普通仙丹 furnace = new GoldenPillFurnace(); ImmortalPill pill2 = furnace.makePill(); // 向上转型 System.out.println(pill2); // 九转金丹 }}
协变返回的特点- 返回类型可以更具体
- 子类方法可以返回父类方法返回类型的子类
- 如知识库例子:WheatMill.process()返回Wheat而不是Grain
- 保持类型安全
- 编译器保证兼容性
- 不会破坏多态
- Java 5之前的痛苦
- // 以前必须这样
class OldFurnace { ImmortalPill makePill() { return new ImmortalPill(); } }
class OldGoldenFurnace extends OldFurnace { @Override ImmortalPill makePill() { // 不能返回GoldenPill return (ImmortalPill) new GoldenPill(); // 需要转型 } }
第六章:替代与扩展——纯粹vs实用这是设计哲学的选择:纯粹替代(is-a)还是扩展接口(is-like-a)? 纯粹替代(is-a关系)// Shape例子:纯粹替代abstract class Shape { abstract void draw(); abstract void erase();}class Circle extends Shape { @Override void draw() { System.out.println("Drawing circle"); } @Override void erase() { System.out.println("Erasing circle"); } // 没有额外方法}public class PureSubstitution { static void process(Shape shape) { shape.draw(); shape.erase(); } public static void main(String[] args) { process(new Circle()); // 完美替代 }}
优点: - 完全透明:使用者不需要知道具体类型
- 易于扩展:新增类型不影响现有代码
- 符合原则:里氏替换原则
扩展接口(is-like-a关系)class BasicImmortal { void basicSkill() { System.out.println("Basic immortal skill"); }}class SwordMaster extends BasicImmortal { // 扩展新方法 void swordSkill() { System.out.println("Sword mastery"); }}public class ExtensionDemo { public static void main(String[] args) { BasicImmortal[] immortals = { new BasicImmortal(), new SwordMaster() }; for (BasicImmortal immortal : immortals) { immortal.basicSkill(); // immortal.swordSkill(); // 编译错误! // 需要向下转型 if (immortal instanceof SwordMaster) { ((SwordMaster) immortal).swordSkill(); } } }}
如何选择?看知识库的指导知识库说:"使用继承表达行为的差异,使用属性表达状态的变化。" // 更好的设计:组合代替继承class SmartImmortal { private BasicSkills basic = new BasicSkills(); private SwordSkills sword = new SwordSkills(); void doEverything() { basic.execute(); sword.execute(); }}// 或者:接口组合interface CanFly { void fly();}interface CanFight { void fight();}class CompleteImmortal implements CanFly, CanFight { @Override public void fly() { System.out.println("Flying"); } @Override public void fight() { System.out.println("Fighting"); }}
总结:多态的六重境界- 向上转型:安全升天,接口可能缩小
- 向下转型:危险下凡,需要类型检查
- 转机:后期绑定,多态的灵魂
- 构造器与多态:先筑基后修炼,小心陷阱
- 协变返回:子类更具体,类型安全
- 替代vs扩展:纯粹还是实用,根据场景选择
记住贫道的真言: - 向上转型自动安全,向下转型小心异常
- 多态靠后期绑定,构造器里别乱调用
- 协变返回到具体,设计要选对方向
多态用好了,代码如行云流水;用不好,bug如影随形。各位道友,修行路上要步步为营啊! 查看详情:https://www.toutiao.com/article/7581087477377663526 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |