8.多态

72 阅读4分钟

第8章 多态

8.1 再论向上转型

public class Instrument {
    public void play(String note){
        System.out.println("Instrument play "+note);
    }
}

public class Brass extends Instrument {
    @Override
    public void play(String note) {
        System.out.println("Brass play " +note);
    }
}

public class Wind extends Instrument {
    @Override
    public void play(String note) {
        System.out.println("Wind play " +note);
    }
}

// 非多态的使用,可以看到代码更臃肿,而且在添加乐器类时,必须添加对应的重载方法
public class Music {
    public static void tune(Brass b){
        b.play("音符");
    }
    public static void tune(Wind w){
        w.play("音符");
    }
    public static void main(String[] args) {
        tune(new Wind());
        tune(new Brass());
    }
}

// 向上转型的使用
public class Music {
    public static void tune(Instrument i){
        i.play("音符");
    }

    public static void main(String[] args) {
        tune(new Wind());
        tune(new Brass());
    }
}

8.2 转机

绑定 将一个方法调用 同 一个方法主体关联起来。

前期绑定 在程序执行前进行绑定。

后期绑定/动态绑定/运行时绑定 在运行时根据对象的类型进行绑定。

public class Shape {
    public void draw(){
        System.out.println("几何图形");
    }
}

public class Circle extends Shape{
    @Override
    public void draw() {
        System.out.println("圆形");
    }
}

public class Square extends Shape {
    @Override
    public void draw() {
        System.out.println("正方形");
    }
}

public class Triangle extends Shape {
    @Override
    public void draw() {
        System.out.println("矩形");
    }
}

public class RandomShape {
    private static Random random = new Random(47);

    // 产生随机几何图形
    public static Shape next() {
        switch (random.nextInt(3)) {
            case 0:
                return new Circle();
            case 1:
                return new Square();
            default:
                return new Triangle();
        }

    }

    public static void main(String[] args) {
        Shape[] shapes = new Shape[5];
        for (int i = 0; i < shapes.length; i++) {
            // 将随机产生的几何图形放入Shape数组中
            shapes[i] = next();
        }
        for (Shape shape : shapes) {
            // 正确画出图形
            shape.draw();
            /*
            矩形
			矩形
			正方形
			矩形
			正方形
			*/
        }
    }
}

总结:

结合8.1和8.2的案例可以得出以下结论:只与基类通信,其一可以得到正确的结果;其二,程序是可扩展的,而不用更改现有代码。

注意: 私有方法/静态方法/域不存在多态。

8.3 构造器和多态

构造器的调用顺序

public class Animal {
    public Animal(){
        System.out.println("Animal");
    }
}

public class Person extends Animal {
    public Person(){
        System.out.println("Person");
    }
}

public class Student extends Person {
    private Table table = new Table();
    public Student(){
        System.out.println("Student");
    }
}

   @Test
    public void test1(){
        Student student = new Student();
    }
/*
Animal
Person
Table
Student
*/

总结:

先调用积累构造器,再调用成员的初始化方法,最后调用类构造器,这样保证了所有成员都已经构建完毕。

继承与清理

当我们需要处理一些清理问题时。

public class Table  {
    public Table(){
        System.out.println("Table");
    }
    public void dispose(){
        System.out.println("Table被销毁");
    }
}

public class Book {
    public Book(){
        System.out.println("Book");
    }

    public void dispose(){
        System.out.println("Book被销毁");
    }
}

public class Animal {
    public Animal(){
        System.out.println("Animal");
    }

    public void dispose(){
        System.out.println("Animal被销毁");
    }
}

public class Person extends Animal {
    public Person(){
        System.out.println("Person");
    }

    @Override
    public void dispose() {
        System.out.println("Person被销毁");
        super.dispose();
    }
}

public class Student extends Person {
    private Table table = new Table();
    private Book book = new Book();
    public Student(){
        System.out.println("Student");
    }

    /*
    注意清理的顺序:
    按照声明倒序清理成员;
    清理本对象;
    清理父类对象
    */
    @Override
    public void dispose() {
        book.dispose();
        table.dispose();
        System.out.println("Student被销毁");
        super.dispose();
    }
}

  @Test
    public void test1(){
        Student student = new Student();
        student.dispose();
    }
/*
Animal
Person
Table
Book
Student
Book被销毁
Table被销毁
Student被销毁
Person被销毁
Animal被销毁
*/

当一个对象被多处引用时的处理

public class Toy {
    // 定义引用计数器
    private int recount =0;
    // 定义产生的对象的个数
    private static int count = 0;
    // 定义对象的id
    // 这里执行了两步操作,先将对象计数器+1,然后将该值作为该对象的id
    private final int id = ++count;
    public Toy(){
        System.out.println("创建:"+this);
    }

    // 增加引用
    public void addRef(){
        recount++;
    }
    public void dispose(){
        // 减去一引用,判断是否还有其他引用
        if (--recount <= 0){
            System.out.println("Toy"+id+"号被销毁");
        }
    }

    @Override
    public String toString(){
        return "Toy"+id+"号";
    }
}

public class Children {

    private Toy toy;
    public Children(Toy toy){
        this.toy = toy;
        // 增加引用
        this.toy.addRef();
    }

    public void dispose(){
        toy.dispose();
        System.out.println("Children被销毁");
    }
}

 @Test
    public void test1(){
        Toy toy = new Toy();
        Children[] childrens = {new Children(toy),new Children(toy),new Children(toy)};
        for (Children children : childrens) {
            children.dispose();
        }
    }
/*
创建:Toy1号
Children被销毁
Children被销毁
Toy1号被销毁
Children被销毁
*/

构造器内部的多态方法行为

public class Animal {
    public Animal(){
        System.out.println("Animal");
        eat();
    }
    public void eat(){
        System.out.println("Animal吃东西");
    }
}

public class Person extends Animal {
     private String food = "盖饭";
    public Person(){
        System.out.println("Person");
    }
    @Override
    public void eat() {
        System.out.println("人类吃"+food);
    }
}

 @Test
    public void test1(){
        Person person = new Person();
    }
/*
Animal
人类吃null
Person
*/

总结:

在初始化过程中,当基类调用被重写的方法时,基类会调用重写后的方法。

之前的初始化过程是不完整的,真正的初始化是将分配给对象的空间初始化为二进制的0,再按照之前的初始化过程进行初始化。

构造器中调用final或private方法时不会出现上述问题。

构造器中应当尽可能简单。

8.4 协变返回类型

在子类覆盖基类方法时,可以返回基类返回类型的子类型。

8.5 用继承进行设计

相对于继承,更优的选择是组合。

组合可以在运行期间选择选择具体的对象,而继承不能做到这一点。

public class Person  {
    public void eat(){
        System.out.println("人吃饭");
    }
}

public class Student extends Person {

    @Override
    public void eat() {
        System.out.println("学生吃饭");
    }
}

public class Teacher extends Person {
    @Override
    public void eat() {
        System.out.println("老师吃饭");
    }
}

public class School {
    private Person person = new Student();

    public void action() {
        person.eat();
    }

    public void change() {
        person = new Teacher();
    }
}

 @Test
    public void test1(){
        School school = new School();
        school.action();
        school.change();
        school.action();
    }
/*
学生吃饭
老师吃饭
*/

纯继承与扩展

导出类与基类具有完全一致的接口时,是Is-a关系,此时是纯替代关系,是安全的;当导出类扩充方法了,是is-like-a的关系,基类并不能访问导出类特有的方法,此时向下转型访问特有的方法是错误的。