java[14多态]

多态:

      在面向对象程序设计中多态是一个非常重要的特性,理解多态有利于进行面向对象的分析与设计。

      发生多态要有三个前提条件:

  1. 继承。多态发生一定要子类和父类之间。

     2. 覆盖。子类覆盖了父类的方法。

     3. 声明的变量类型是父类类型,但实例则指向子类实例。

     下面通过一个示例理解什么多态。如图 12-5 所示,父类 Figure(几何图形)类有一个 onDraw(绘图) 方法, Figure(几何图形) 它有两个子类 Ellipse(椭圆形)和 Triangle(三角形), Ellipse 和 Triangle 覆盖 onDraw 方法。 Ellipse 和 Triangle 都有 onDraw 方法,但具体实现的方式不同。

具体代码如下:

//Figure.java文件

package com.a51work6;

public class Figure {

    //绘制几何图形方法

    public void onDraw() {

    System.out.println("绘制Figure...");

    }

}

//Ellipse.java文件

package com.a51work6;

//几何图形椭圆形

public class Ellipse extends Figure {

    //绘制几何图形方法

    @Override

    public void onDraw() {

    System.out.println("绘制椭圆形...");
    
    }

}

//Triangle.java文件

package com.a51work6;

//几何图形三角形

public class Triangle extends Figure {

    // 绘制几何图形方法

    @Override

    public void onDraw() {

    System.out.println("绘制三角形...");

    }

}

调用代码如下:

//HelloWorld.java文件

package com.a51work6;

public class HelloWorld {

    public static void main(String[] args) {

        // f1变量是父类类型, 指向父类实例

        Figure f1 = new Figure(); ①

        f1.onDraw();

        //f2变量是父类类型, 指向子类实例,发生多态

        Figure f2 = new Triangle(); ②

        f2.onDraw();
    
        //f3变量是父类类型, 指向子类实例,发生多态

        Figure f3 = new Ellipse(); ③

        f3.onDraw();

        //f4变量是子类类型, 指向子类实例

        Triangle f4 = new Triangle(); ④

        f4.onDraw();

    }

}

     上述带代码第②行和第③行是符合多态的三个前提,因此会发生多态。而代码第①行和第④行都不符合,没有发生多态。

运行结果如下:

绘制Figure...

绘制三角形...

绘制椭圆形...

绘制三角形...

     从运行结果可知,多态发生时, Java 虚拟机运行时根据引用变量指向的实例调用它的方法,而不是根据引用变量的类型调用。

引用类型检查

     有时候需要在运行时判断一个对象是否属于某个引用类型,这时可以使用 instanceof运算符, instanceof 运算符语法格式如下:

obj instanceof type

     其中 obj 是一个对象, type 是引用类型,如果 obj 对象是 type 引用类型实例则返回true, 否则 false。

 

     继承层次树中具体实现代码如下:

//Person.java文件

package com.a51work6;

public class Person {

    String name;

    int age;

    public Person(String name, int age) {

        this.name = name;

        this.age = age;

    }

    @Override

    public String toString() {

        return "Person [name=" + name

        + ", age=" + age + "]";

    }

}

//Worker.java文件

package com.a51work6;

public class Worker extends Person {

    String factory;

    public Worker(String name, int age, String factory) {

        super(name, age);

        this.factory = factory;

    }

    @Override

    public String toString() {

        return "Worker [factory=" + factory

        + ", name=" + name

        + ", age=" + age + "]";

    }

}

//Student.java文件

package com.a51work6;

public class Student extends Person {

    String school;

    public Student(String name, int age, String school) {
    
        super(name, age);

        this.school = school;

    }

    @Override

    public String toString() {

        return "Student [school=" + school

        + ", name=" + name

        + ", age=" + age + "]";

    }

}

调用代码如下:

//HelloWorld.java文件

package com.a51work6;

public class HelloWorld {

    public static void main(String[] args) {

        Student student1 = new Student("Tom", 18, "清华大学"); ①

        Student student2 = new Student("Ben", 28, "北京大学");

        Student student3 = new Student("Tony", 38, "香港大学"); ②

        Worker worker1 = new Worker("Tom", 18, "钢厂"); ③

        Worker worker2 = new Worker("Ben", 20, "电厂"); ④

        Person[] people = { student1, student2, student3, worker1, worker2 }; ⑤

        int studentCount = 0;

        int workerCount = 0;

        for (Person item : people) { ⑥
        
            if (item instanceof Worker) { ⑦

                workerCount++;

                } else if (item instanceof Student) { ⑧

                    studentCount++;

                }

            }

        System.out.printf("工人人数: %d,学生人数: %d", workerCount, studentCount);

    }

}

     上述代码第①行和第②行创建了 3 个 Student 实例, 代码第③行和第④行创建了两个Worker 实例, 然后程序把这 5 个实例放入 people 数组中。

     代码第⑥行使用 for-each 遍历 people 数组集合,当从 people 数组中取出元素时,元素类型是 People 类型,但是实例不知道是哪个子类(Student 和 Worker) 实例。 代码第⑦行 item instanceof Worker 表达式是判断数组中的元素是否是 Worker 实例;类似地,第⑧行 item instanceof Student 表达式是判断数组中的元素是否是 Student 实例。

     输出结果如下:

工人人数: 2,学生人数: 3

引用类型转换

     数值类型可以相互转换,引用类型可以进行转换,但并不是所有的引用类型都能互相转换,只有属于同一颗继承层次树中的引用类型才可以转换。

HelloWorld.java 代码如下:

//HelloWorld.java文件

package com.a51work6;

public class HelloWorld {

    public static void main(String[] args) {

        Person p1 = new Student("Tom", 18, "清华大学");

        Person p2 = new Worker("Tom", 18, "钢厂");

        Person p3 = new Person("Tom", 28);
    
        Student p4 = new Student("Ben", 40, "清华大学");

        Worker p5 = new Worker("Tony", 28, "钢厂");

        …

    }

}

     上述代码创建了 3 个实例 p1、 p2、 p3、 p4 和 p5, 它们的类型都是 Person 继承层次树中的引用类型, p1 和 p4 是 Student 实例, p2 和 p5 是 Worker 实例, p3 是 Person 实例。

     首先, 对象类型转换一定发生在继承的前提下, p1 和 p2 都声明为 Person 类型,而实例是由 Person 子类型实例化的。表 12-1 归纳了 p1、 p2、 p3、 p4 和 p5 这 5 个实例与 Worker、 Student 和 Person 这 3种类型之间的转换关系。

     作为这段程序的编写者是知道 p1 本质上是 Student 实例,但是表面上看是 Person 类型,编译器也无法推断 p1 的实例是 Person、Student 还是 Worker。此时可以使用 instanceof操作符来判断它是哪一类的实例。

     引用类型转换也是通过小括号运算符实现,类型转换有两个方向:将父类引用类型变量转换为子类类型, 这种转换称为向下转型(downcast);将子类引用类型变量转换为父类类型,这种转换称为向上转型(upcast)。向下转型需要强制转换,而向上转型是自动的。

     下面通过示例详细说明一下向下转型和向上转型,在 HelloWorld.java 的 main 方法中

添加如下代码:

// 向上转型
Person p = (Person) p4; ①
// 向下转型
Student p11 = (Student) p1; ②
Worker p12 = (Worker) p2; ③
// Student p111 = (Student) p2; //运行时异常 ④
if (p2 instanceof Student) {
Student p111 = (Student) p2;
}
// Worker p121 = (Worker) p1; //运行时异常 ⑤
if (p1 instanceof Worker) {
Worker p121 = (Worker) p1;
}
// Student p131 = (Student) p3; //运行时异常 ⑥
if (p3 instanceof Student) {
Student p131 = (Student) p3;
}




     上述代码第①行将 p4 对象转换为 Person 类型, p4 本质上是 Student 实例, 这是向上转型,这种转换是自动的,其实不需要小括号(Person)进行强制类型转换。代码第②行和第③行是向下类型转换,它们的转型都能成功。而代码第④、⑤、⑥行都会发生运行时异常 ClassCastException,如果不能确定实例是哪一种类型,可以在转型前使用 instanceof 运算符判断一下。

猜你喜欢

转载自blog.csdn.net/qq_38125626/article/details/81093280