引言
多态是面向对象编程的三大特性之一,也是 Java 中最能体现“抽象与灵活”的设计思想。本文将从重写与重载的区别讲起,逐步深入向上转型、向下转型,以及动态绑定与静态绑定的底层机制,帮助你真正理解多态的本质。
文章目录
- 引言
- 1.1 多态
- 多态的条件
- 1.2 重写(Override)
- 重写规则
- 重写与重载的区别
- 1.3 向上转型
- 向上转型使用场景
- 1.4 向下转型
- 1.5 instanceof 关键字的使用
- 1.6 动态绑定与静态绑定
- 静态绑定(编译时)
- 动态绑定(运行时)
- 动态绑定 vs 静态绑定
- 多态优点
1.1 多态
多态的定义:同一操作作用于不同对象,产生不同的执行结果
多态的条件
继承、重写、通过父类的引用调用重写的方法
多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。
packagedemo1;//父类publicclassAnimal{publicStringname;publicintage;publicAnimal(Stringname,intage){this.name=name;this.age=age;}publicvoideat(){System.out.println(this.name+"吃饭");}}publicclassCatextendsAnimal{publicCat(Stringname,intage){super(name,age);}@Overridepublicvoideat(){System.out.println(this.name+"吃鱼");}}publicclassDogextendsAnimal{publicDog(Stringname,intage){super(name,age);}@Overridepublicvoideat(){System.out.println(this.name+"吃骨头");}}publicclassmain{//这里形参必须是父类 能够接收子类publicstaticvoideat(Animalanimal){animal.eat();}//编译器在编译代码时 不知道要调子类中谁的eat的⽅法//这里是动多态 详细可看动态绑定publicstaticvoidmain(String[]args){Catc=newCat("uu",2);Dogd=newDog("ww",5);eat(c);eat(d);}}//运行结果:uu吃鱼 ww吃骨头1.2 重写(Override)
重写的定义:子类重新实现父类中已有的方法
重写规则
- 必须与父类方法保持一致 :
返回值类型 方法名(参数) - 重写的返回值类型可以不同,但是必须是父子关系(协变类型)
publicAnimaleat(){System.out.println(this.name+"吃饭");returnnull;}@OverridepublicDogeat(){System.out.println(this.name+"吃骨头");returnnull;}- 访问权限 子类要>=父类
- 被static,final,private修饰的方法都不能被重写
static方法属于类本身,不是属于对象实例的,而重写 是面向对象里「子类对象覆盖父类对象的实例方法」的机制final方法的实现逻辑是父类定死的,不允许子类改写/重写,防止子类通过重写破坏父类设计。private只能访问一个类,重写跨类了
@override注解可以对重写方法进行合法性校验,比如方法名写错之类的
重写与重载的区别
【重载】同一个类中方法名相同、参数列表不同(参数个数、类型、顺序至少一个不同)
| 对比维度 | 重写(Override) | 重载(Overload) |
|---|---|---|
| 发生位置 | 子类中 | 同一个类中 |
| 方法名 | 必须相同 | 必须相同 |
| 参数列表 | 必须相同 | 必须不同(个数、类型、顺序) |
| 返回值类型 | 相同或协变类型 | 可以不同 |
| 访问权限 | 子类不能低于父类 | 无限制 |
| 修饰符 | 不能是 static、final、private | 无限制 |
| 绑定方式 | 动态绑定(运行时) | 静态绑定(编译期) |
| 目的 | 子类重新实现父类方法 | 提供多个同名但参数不同的方法 |
1.3 向上转型
向上转型的定义:子类对象赋值给父类引用
向上转型使用场景
- 直接赋值
publicclassAnimal{//省略了}publicclassCatextendsAnimal{//省略了}//向上转型Animala=newCat();- 方法传参
publicclassmain{//这里形参必须是父类 能够接收子类publicstaticvoideat(Animalanimal){animal.eat();}publicstaticvoidmain(String[]args){//传参eat(newCat("uu",2));}}- 方法返回
publicstaticAnimalgetAnimal(){returnnewDog();// 向上转型 Dog → Animal}向上转型后只能调用父类中声明的方法
//父类publicclassAnimal{publicStringname;publicintage;publicAnimal(Stringname,intage){this.name=name;this.age=age;}publicvoideat(){System.out.println(this.name+"吃饭");}}//子类publicclassCatextendsAnimal{publicCat(Stringname,intage){super(name,age);}@Overridepublicvoideat(){System.out.println(this.name+"吃鱼");}//子类独有的方法publicvoidrun(){System.out.println(this.name+"逛街");}}publicclassmain{publicstaticvoidmain(String[]args){Animala1=newCat("mimi",5);Animala2=newDog("kimi",4);a1.eat();//这里报错 Animal类中没有run⽅法a1.run();}}1.4 向下转型
- 向下转型的定义:父类引用强制转换为子类类型
- Why:由于向上转型无法调用子类独有的方法,所以向下转型,简单讲就是还原
向下转型的语法与风险(ClassCastException)
publicclassmain{publicstaticvoidmain(String[]args){Animala1=newCat("mimi",5);Animala2=newDog("kimi",4);a1.eat();//这里报错 Animal类中没有run⽅法a1.run();//此时还原 之前对象是猫 所以还原安全Catcat=(Cat)a1;cat.run();//如果强制还原为狗//会触发ClassCastException异常Dogdog=(Dog)a1;dog.run();1.5 instanceof 关键字的使用
向下转型不安全,所以引入instanceof关键字判断,如果表达式为true,则可以安全转换
publicclassmain{publicstaticvoidmain(String[]args){Animala1=newCat("mimi",5);Animala2=newDog("kimi",4);a1.eat();//判断 如果true就强制转换对应的类型if(a1instanceofCat){Catcat=(Cat)a1;cat.run();}elseif(a1instanceofDog){Dogdog=(Dog)a1;dog.run();}}}1.6 动态绑定与静态绑定
前面讲多态时提到「编译器在编译代码时不知道要调子类中谁的 eat 方法」,这背后就是绑定机制在起作用。
绑定指的是:方法调用与具体方法实现之间建立关联的过程。
静态绑定(编译时)
- 定义:在编译阶段就能确定调用哪个方法,由编译器直接完成绑定。
- 特点:效率高,运行时不需额外判断。
- 适用场景:
static方法、private方法、final方法,以及重载(Overload)。
publicclassCalculator{// 重载:编译期就能确定调用哪个 addpublicintadd(inta,intb){returna+b;}publicdoubleadd(doublea,doubleb){returna+b;}}// 编译时:根据参数类型 (int, int) 直接绑定到第一个 addCalculatorc=newCalculator();c.add(1,2);// 静态绑定 → int addc.add(1.0,2.0);// 静态绑定 → double add动态绑定(运行时)
- 定义:在运行阶段才根据对象的实际类型确定调用哪个方法。
- 特点:灵活,是多态实现的底层机制。
- 适用场景:重写(Override),即通过父类引用调用被子类重写的方法。
publicclassmain{publicstaticvoideat(Animalanimal){// 编译期:只知道 animal 是 Animal 类型// 运行期:根据实际传入的对象(Cat / Dog)动态绑定到对应 eat()animal.eat();}publicstaticvoidmain(String[]args){eat(newCat("uu",2));// 动态绑定 → Cat.eat() 输出 uu吃鱼eat(newDog("ww",5));// 动态绑定 → Dog.eat() 输出 ww吃骨头}}动态绑定 vs 静态绑定
| 对比维度 | 动态绑定 | 静态绑定 |
|---|---|---|
| 绑定时机 | 运行时 | 编译时 |
| 判断依据 | 对象的实际类型 | 变量的声明类型 |
| 对应机制 | 重写 | 重载 |
| 典型方法 | 实例方法(非 final/static/private) | static、private、final 方法 |
小结:多态之所以能「同一操作、不同结果」,正是因为动态绑定在运行时根据对象的真实类型,把方法调用绑定到正确的实现上。
这也是为什么向上转型后调用eat()依然能输出子类的结果——编译器看的是父类引用,运行时绑定的却是子类方法。
多态优点
//父类publicclassScope{publicvoiddraw(){System.out.println("画人体!");}}//子类publicclassbodyextendsScope{@Overridepublicvoiddraw(){System.out.println("身体body就绪!");}}publicclasshandextendsScope{@Overridepublicvoiddraw(){System.out.println("手长出来了!");}}publicclassheadextendsScope{@Overridepublicvoiddraw(){System.out.println("头头头头!");}}publicclasstailextendsScope{@Overridepublicvoiddraw(){System.out.println("隐形尾巴!");}}【未运用多态】
publicclassTest{publicstaticvoidmain(String[]args){body b=newbody();hand a=newhand();head h=newhead();tail t=newtail();String[]scopes={"body","hand","head","tail"};//增强for循环for(Strings:scopes){if("body".equals(s)){b.draw();}elseif("hand".equals(s)){a.draw();}elseif("head".equals(s)){h.draw();}elseif("tail".equals(s)){t.draw();}}}}//运行结果//身体body就绪!//头头头头!//手长出来了!//隐形尾巴!【运用多态】
运用多态则不需要那么多循环,可以简化代码,可扩展性强。
假如我现在要加入一个eyes类,只需要新建类,修改Scope数组,之前未使用多态则需要再写if-else
publicstaticvoidmain(String[]args){Scope[]scopes={newbody(),newhead(),newhand(),newtail()};for(Scopes:scopes){s.draw();}}