Java多态的一些认识
一.什么是多态 ?
- 一些相关介绍:面向对象的编程有三大特性:封装,继承,多态。封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据。在外界看来,暴露的只是他的访问方式。继承是为了重用父类的代码。同时继承也为实现多态做了铺垫。
- 多态的定义: 多态性是面向对象编程的又一个重要特征,它是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。关于多态我们一般有一下两种类型。
- 编译时多态: 编译时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的方法,通过编译之后会变成两个不同的方法。
- 运行时多态:也叫做动态绑定,一般是指在执行期间(非编译期间)判断引用对象的实际类型,根据实际类型判断并调用相应的属性和方法。主要用于继承父类和实现接口时,父类引用指向子类对象。
二.实现多态的条件?
1.继承
2.重写
1)区别重写与重载
重载发生在本类中,重写发生在父类与子类之间。
重载的方法名必须相同,重写的方法名且返回值类型必须相同。
重载的参数列表不同,重写的参数列表必须相同
但是对于重载必须发生在本类中也有争议。子类在某种情况下也可以重载父类的方法。因为子类会继承父类所有的公有方法(构造器方法除外),然后在子类中定义与继承方法同名不同参数列表的方法,这也叫重载。
2) 为什么需要有重写呢?因为子类会继承父类的方法(除构造方法,对于父类的私有方法,子类没有权限调用),为了满足运行时子类的方法在父类上有所拓展,我们选择重写从父类继承的方法。
3. 向上转型:
1) JAVA中的一种调用方式。向上转型是对A的对象的方法的扩充,即A的对象可访问B 从A中继承来的和B“重写”A的方法。通俗来讲就是将子类对象转换为父类对象。
2)比如说我们会在运行时将zi这个对象转换为Fu类对象。
再看另一个例子:
向上转型时会原本是Dog类型的obj转换为Animal类型。在Java中,引用变量可以是多态的,即它可以存放不同类对象的地址,只要这些类是它声明的类的派生类
3)向上转型的一些知识点.
向上转型是不用强制转型的
向上转型时,父类指向子类引用对象会遗失除与父类对象共有的其他方法,也就是在转型过程中,子类的新有的方法都会遗失掉,但是子类重写的方法仍可以调用,只不过此时调用的是子类重写后的方法。向上转型只能使用父类的功能,不能使用子类的一些特定功能。
运行结果
4)动态绑定:父类引用指向的或者调用的方法是子类的方法。
- Eg: Fu obj = new Zi();//向上转型
- obj.show();//动态绑定
4. 向下转型:父类引用的对象转换为子类类型。
1)一般向下转型存在风险,我们将向下转型分为三种情况
第一种如果父类引用的对象如果引用的是指向的子类对象,那么在向下转型的过程中是安全的。也就是编译是不会出错误的。按照上面Animal和Dog的例子,也就是如果引用本来就指向的是Dog,那么向下转型的时候就是安全的。
第二种如果父类引用的对象是父类本身,那么在向下转型的过程中是不安全的,编译不会出错,但是运行时会出现java.lang.ClassCastException错误。它可以使用instanceof来避免出错此类错误。如果引用本来是父类即Animal的一个实例对象,那么向下转型时是不安全的。
第三种情况:
- Fu obj1 = new Fu();
- Zi obj2 = (Zi)obj1;//失效
- 为了避免向下转型的风险,我们一般会提前使用instanceof关键字判断实例的类型,然后再进行转型。
- 向下转型是强制转换。
- 向下转型可以使用子类的特定功能,这一点是向上转型没有的。
5.抽象类
1) 应该注意的是多态可以从不同角度来看待。上面说的转型和重写是一种理解。抽象类和重写又是另一种理解。
2)举一个较易理解的例子,就是图形。图形是一个大类,图形有三角形,矩形,圆等等,因此图形下面有很多具体的图形分类。此时图形就相当于基类,而具体的图形就是派生出来的类,可以明显看到图形这个基类可以有多种形态,这就是多态。同时我们也注意到图形在没有确定具体形状之前,这个基类的方法(比如求面积)大多不能具体确定。
- //创建基类——图形,注意类头加上abstract标识符
- public abstract class GeometricObject{
- private boolean filled;
- private java.util.Date dateCreated;
- //抽象类的构造方法定义为protected,因为它只被子类使用。创建一个具体的实例时,其父类的构造方法被调用以初始化父类中定义的数据域
- protected GeometricObject(){
- dateCreated = new java.util.Date();
- }
- //创建时间
- public java.util.Date getDateCreated(){
- return dateCreated;
- }
- public abstract double getArea();//抽象方法,获取面积
- public abstract double getPerimeter();//抽象方法,获取周长
- }
- 创建派生类——圆
- public class Circle extends GeometricObject {
- private double radius;
- public Circle() {
- }
- public Circle(double radius) {
- this.radius = radius;
- }
- /** Return radius */
- public double getRadius() {
- return radius;
- }
- /** Set a new radius */
- public void setRadius(double radius) {
- this.radius = radius;
- }
- @Override /** Return area */
- //在子类中实现基类中的抽象方法
- public double getArea() {
- return radius * radius * Math.PI;
- }
- @Override /** Return perimeter */
- //在子类中实现基类中的抽象方法
- public double getPerimeter() {
- return 2 * radius * Math.PI;
- }
- }
- 创建派生类——矩形
- public class Rectangle extends GeometricObject {
- private double width;
- private double height;
- public Rectangle() {
- }
- public Rectangle(double width, double height) {
- this.width = width;
- this.height = height;
- }
- @Override /** Return area */
- //在子类中实现基类中的抽象方法
- public double getArea() {
- return width * height;
- }
- @Override /** Return perimeter */
- //在子类中实现基类中的抽象方法
- public double getPerimeter() {
- return 2 * (width + height);
- }
- }
- 下面是Demo
- public class Test{
- public static void main(String[] args){
- GeometricObject obj1 = new Circle(5);
- GeometricObject obj2 = new Rectangle(5, 3);
- System.out.println("面积是:“ + obj1.getArea() + " 周长是:” +
- obj1.getPerimeter());
- System.out.println("面积是:“ + obj2.getArea() + " 周长是:” +
- obj2.getPerimeter());
- }
3)关于抽象类的一些注意事项
抽象类的构造方法要用protected标识符。抽象类的构造方法在子类调用构造方法时会被调用。
不能用new创建关于抽象类的实例(如果是正常的父类,我们可以用new去创建一个父类的实例)。抽象类只能为其他类的基类。在创建子类的时候一般是 父类类型 变量名 = new 子类类型()。
若父类是抽象类,如果子类不想成为抽象类,那么子类必须将父类中的抽象方法重写为带方法体的普通方法,否则子类仍然是抽象类(例如上面的多边形类)
抽象类可以作为一种数据类型。我们可以创建一个抽象类类型的数组,然后再创建具体实例。
- //创建一个GeometricObject类型的数组,并将引用赋给arr.
- GeometricObject[] arr = new GeometricObject[10];
- //创建GeometricObject的子类实例,并将引用赋给数组
- arr[0] = new Circle();
- arr[1] = new Rectangle();
三.多态
1. 抽象类多态定义的使用格式
父类类型 变量名 = new 子类类型();
Eg:
2.多态的相关语法
- 在实现多态时其实就是因为子类继承父类的方法,然后子类继承父类的方法(除了构造函数和私有方法不能个访问),对于这些方法子类会进行重写。对于父类来说可以有很多拓展的子类,因此对于父类的方法,会有重写的很多不同方法。
- 关于子类重写父类的方法和属性,我们要区分两个概念覆盖和隐藏。
- 隐藏:child隐藏了parent的变量和方法,那么,child不能访问parent被隐藏的变量或者方法,但是,将child转换成parent中,可以访问parent被隐藏的变量或者方法
- 覆盖:child覆盖了parent的变量或者方法,那么,child不能访问parent被覆盖的变量或者方法,将child转换成parent后同样不能访问parent被覆盖的变量或者方法
- 总结:变量只会隐藏不会被覆盖,无论是实例变量还是静态变量。即子类的变量可以隐藏父类的变量。对于方法而言,同名的实例方法被覆盖,同名的静态方法被隐藏。
运行结果
3.应用场景
1)使用循环,使基类访问不同的派生类
- Eg: GradedActivity[] tests = new GradedActivity[3];//共有三个派生类
- //第一次采用五级计分制,考了75
- test[0] = new GradedActivity();
- test[0].setScore(75);
- //第二次考试采用二级积分制(P或者F)总共二十题,每题分值相同,
- //共二十道题,考生错了五道题。通过的最低分为60分
- test[1] = new PassFailExan(20, 5 ,60);
- //第三次考试是期末考试,也采用五级计分制,总共50题,每 题分值相同,考生答错了7道
- test[2] = new FinalExam(50, 7);
- //显示每次考试的分数和等级
- for(int i = 0; i<tests.length; i++){
- System.out.println("Score:" +
- tests[i].getScore() + "\t" + "Grade:" + exam.getGrade());
- }
2)实参是 派生类,形参是基类
- Eg: public static void main(String[] args) {
- GradedActivity[] tests = new GradedActivity[3];
- // 第一次考试采用五级计分制,考了75
- tests[0] = new GradedActivity();
- tests[0].setScore(75);
- // 第二次考试采用二级计分制(P或者F)。总共20题,每题分值相同,考生答错5题。
- // 通过的最低分数线是60分
- tests[1] = new PassFailExam(20, 5, 60);
- // 第三次是期末考试也采用五级计分制. 总共50题,每题分值相同,考试答错7题
- tests[2] = new FinalExam(50, 7);
- // 显示每次考试的分数和等级
- for(int i=0; i<tests.length; i++){
- showValue(tests[i]);//子类作为参数,传实参
- }
- }
- public static void showValue(GradedActivity exam){
- //父类作为形参
- System.out.println("Score: " + exam.getScore()+ "\t" +
- "Grade: " + exam.getGrade());
- }