当前位置: 首页 > news >正文

【JAVA】java多态

多态和继承 封装 并成为面向对象的三大特性

一:多态的概念

多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同 的状态。
总的来说:同一件事情,发生在不同对象身上,就会产生不同的结果。

1.1: 多态实现条件

在java中要实现多态,必须要满足如下几个条件,缺一不可:
1. 必须在继承体系下(必须要有子父类,java依赖、python的多态不依赖继承,c++中的静态多态不依赖继承,动态多态依赖继承)
2. 子类必须要对父类中方法进行重写
(重写:子类和父类的关系,父类中有一个方法,但子类中也提供同样的方法(参数列表,方法名字,返回值的类型要相容 都是一样的))1:类型完全相同 2:子类重写的方法的返回值类 是父类对应方法的返回值类型的子类。
3. 通过父类的引用调用重写的方法

public class Son {  public void eat(){  System.out.println("Son 吃东西");  }  
}public class XiaoTang extends Son{  public void eat(){  System.out.println("xiaotang 爱吃米饭");  }  
}
public class Yuanyuan extends Son{  public void eat(){  System.out.println("yuanyuan 在吃米饭");  }  
}public class Test {  public static void main(String[] args) {  Son son = new Son();  son.eat();  //使用父类的引用,指向一个字子类的实例 这样的语法是完全可以的  //XiaoTang xiaotang= new XiaoTang();意思相同  son=new XiaoTang();  son.eat();  son = new Yuanyuan();  son.eat();  //表面上看 都是son.eat但要看执行过程中 执行了完全不同的逻辑  //具体son.eat()执行那个逻辑 要结合上下文 明确son到底是谁。  }  
}
运行结果:
//Son 吃东西
//xiaotang 爱吃米饭
//yuanyuan 在吃米饭

如果写成这样,这个就不是多态,这个就是引用
分析一下son.eat:
代码执行到这个地方,jvm就会在运行的过程中,分析son真实指向的对象是什么类型,进一步的调用匹配的类型方法(eat方法执行那个版本,是程序运行来确定,不是编译过程确定的。)
编译过程:就是生成字节码的过程
运行时:字节码生成好之后,运行来确定执行那个调用,那个方法。
java中的多态,就是基于动态绑定,这样的机制来实现,

在上述代码中, 分割线上方的代码是 类的实现者 编写的, 分割线下方的代码是 类的调用者 编写的.
当类的调用者在编写 eat 这个方法的时候, 参数类型为 Animal (父类), 此时在该方法内部并不知道, 也不关注当前的a 引用指向的是哪个类型(哪个子类)的实例. 此时 a这个引用调用 eat方法可能会有多种不同的表现(和 a 引用的实例相关), 这种行为就称为 多态.

另外一种写法:

1.2:重写

重写(override):父类子类,方法名返回值参数都一样
也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程
进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。

2overrides,该方法被两次重写

【方法重写的规则】
1子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致

2被重写的方法返回值类型可以不同,但是必须是具有父子关系的

3访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为 protected

4父类被static、private修饰的方法、构造方法都不能被重写。

5重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法构成重写.

【重写的设计原则】
对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容,并且添加或者改动新的内容

静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。
动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。

1.3: 向上转移和向下转型

1:向上转型

向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。
语法格式:父类类型 对象名 = new 子类类型()

animal是父类类型,但可以引用一个子类对象,因为是从小范围向大范围的转换。
父类引用指向子类实例

左边父类 右边子类,

package object3;  public class Test {  public static void  func(Animal animal){//(2)  animal.eat();  }  public static Animal creatAnimal(){//(3)  return new Cat();  }  public static void main(String[] args) {  Animal animal = new Cat();//向上转型  animal.eat();  func(new Cat());//第二种情况  //第三种(3)  //Animal animal= creatAnimal();  }  
}

2:向下转型

将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换。


子类有这个方法,但是父类没有,虽然animal确实时指向子类的实例,但是类型却是父类的类,编译过程中,无法找到meao的方法。

编译器编译的时候,看到的animal引用就是父类类型的引用,尝试在父类中寻找sleep,但是没有,在编译阶段,不知道animal指向的真实类型。

 Animal animal = new Cat();//向上转型  
Cat cat = (Cat) animal;//在向下转强转
cat.mewo();//然后调用

这样就解决了。

还有一个例子图上;

向下转型的时候,需要确保,当前这里的父类引用确实是指向子类实例的,如果不是就算强转,也无法调用里面的方法。
虽然没有报错但出现异常了

解决办法:

        Animal animal = new Animal();  
//        Cat cat = (Cat) animal;//在向下转  
//        cat.mewo();//然后调用  //ClassCastException:为了避免出现这个异常,我们可以显式使用instanceof关键字进行类型判断  if(animal instanceof Cat){  Cat cat = (Cat) animal;//在向下转  cat.mewo();  }

1.4:多态的优缺点

优点:降低代码的圈复杂度、
圈复杂度:衡量一个代码/方法复杂程度的一种指标
如果中国代码从上到下直接执行完成,计算简单代码
随着代码中 条件越来越多 循环越来越多 代码理解起来更费劲
把一段代码中出现分支的个数=“称为圈复杂度”

如果使用使用多态, 则不必写这么多的 if - else 分支语句, 代码更简单
Test类()测试类

package object3.package2;  public class Test {  public static void main(String[] args) {  //给定一个数组,表示要打印的性状序列  
//        String[] shapes={"circle","rect","rect","flower"};  
//        for(String shapeName:shapes){//for-each  
//            if(shapeName.equals("circle")){//比较两个字符串相等,需要使用equals,不能使用==  
//                Shape shape=new Cycle();  
//               shape.draw();  
//            } else if (shapeName.equals("rect")) {  
//                Shape shape =new Rect();  
//                shape.draw();  
//            } else if (shapeName.equals("flower")) {  
//                Shape shape=new Flower();  
//                shape.draw();  
//            }  //}        //基于多态,优化上述代码  Shape[] shapes={new Cycle(),new Rect(),new Rect(),new Flower(),new Triangle()};  //由于此处的Cycle rect flower类类型是Shape的子类,子类的引用可以放到父类的数组当中  for(Shape shape :shapes){  //此时不用写太多if else 直接通过动态绑定,调用对应的draw从而绘制  shape.draw();  }  }  
}
public class Triangle extends Shape{  public void draw(){  System.out.println("▲");  }  
}
public class Flower extends Shape {  public void draw(){  System.out.println("❀");  }  
}
public class Cycle extends Shape{  public void draw(){  System.out.println("⚪");  }  
}
public class Rect extends Shape {  public  void draw(){  System.out.println("□");  }  
}
父类
public class Shape {  public void draw(){  System.out.println("绘制形状");  }  
}

2:可扩展能力更强(最后的三角形类是手动添加)
如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低.
3:多态是使用者不光不需要知道类的内部空间,甚至都不需要知道这个类的真实类型,就可以使用。

缺点:程序运行过程中,消耗更多的时间,(运行效率低)和空间(消耗的内存更多)
多态绑定,需要一定开销。
但是利大于弊。

1.5:避免在构造方法中调用重写的方法

class B {public B() {// do nothingfunc();}public void func() {System.out.println("B.func()");}}class D extends B {private int num = 1;@Overridepublic void func() {System.out.println("D.func() " + num);}}public class Test {public static void main(String[] args) {D d = new D();}}// 执行结果D.func() 0

构造 D 对象的同时, 会调用 B 的构造方法.

B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func

此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0. 如果具备多态性,num的值应该是1.

所以在构造函数内,尽量避免使用实例方法,除了final和private方法。

结论: "用尽量简单的方式使对象进入可工作状态", 尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题

二:总结

本文主要介绍了面向对象编程中的多态特性。多态指同一行为在不同对象上产生不同结果,实现条件包括继承体系、方法重写和父类引用调用。文章详细讲解了方法重写规则、向上转型与向下转型的应用场景,并通过实例代码展示了多态的实现方式。同时分析了多态的优点(降低复杂度、提高扩展性)和缺点(运行效率稍低),特别强调了在构造函数中避免调用重写方法的注意事项。最后总结了多态是面向对象编程的重要特性,能有效提升代码的灵活性和可维护性。

http://www.dtcms.com/a/395162.html

相关文章:

  • 【AI分析进行时】大模型显存需求估算与国内开源模型实践指南
  • C++基础:(一)C++入门知识介绍(上)
  • Python项目的多语言翻译babel
  • python flask框架详解
  • 基于STM32单片机的家庭医护血氧体温血压吃药监测APP系统
  • 整合亮数据Bright Data与Dify构建自动化分析系统
  • Browser-Use+cpolar:企业网页操作自动化的无界解决方案
  • 深入理解 Elasticsearch:核心原理、性能优化与高频面试题解析
  • 【C++】Lambda表达式参数问题
  • 数学金融方向要额外学什么课?这个专业对编程和建模能力要求高吗?
  • 第二部分:VTK核心类详解(第54章 vtkVariantArray变体数组类)
  • 【2025最新】ArcGIS for JS点聚合功能实现
  • Leecode hot100 - 114. 二叉树展开为链表 基础方法到提高方法
  • 把 iOS 混淆纳入自动化测试与 CICD 从构建、回归到灰度的工程化实战
  • 初识Redis:解锁高性能缓存的魔法钥匙
  • 基于传递矩阵法计算多层结构声表面波声速
  • 中间件和分类
  • MV2DFusion:利用模态特定目标语义进行多模态三维检测
  • BeanFactory接口作用(二)
  • 速通ACM省铜第十二天 赋源码(Kirei Attacks the Estate)
  • 海外仓一件代发怎样优化拣货流程?用什么WMS能减少错拣漏拣?
  • SQL Server 定时作业
  • 大模型笔试选择题:题组1
  • 关于STL
  • clickhouse使用问题记录
  • Java 大视界:基于 Java 的大数据实时流处理在金融高频交易数据分析中的创新应用
  • 【脑电分析系列】第25篇:情绪识别与认知研究中的EEG应用:一个完整的实验设计与数据分析流程
  • Tensorflow基础——数据类型、计算图
  • 在Anaconda中安装TensorFlow1.14.0与TensorFlow2.0.0
  • 面试题:分布式锁要点总结(Redisson)