多态

多态: polymorphism

概述

有了封装才有面向对象, 有了面向对象才有继承和多态.

什么是多态: 同类型的对象, 表现出的不同形态.


图1

多态的表现形式: 父类类型 对象名称 = 子类对象;

多态的前提:

  1. 有继承或实现关系 (实现与接口有关)

  2. 有父类引用指向子类对象, Fu f = new Zi();

  3. 要有方法重写.

程序示例:

父类:

public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void show() {
        System.out.println(name + ", " + age);
    }
}

子类:

public class Administrator extends Person {
    @Override
    public void show() {
        System.out.println("管理员的信息为: " + getName() + ", " + getAge());
    }
}

子类:

public class Student extends Person {
    @Override
    public void show() {
        System.out.println("学生的信息为: " + getName() + ", " + getAge());
    }
}

子类:

public class Teacher extends Person {
    @Override
    public void show() {
        System.out.println("老师的信息为: " + getName() + ", " + getAge());
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        // 创建三个对象, 调用 register 方法
        Student s = new Student();
        s.setName("zhangsan");
        s.setAge(23);

        Teacher t = new Teacher();
        t.setName("lisi");
        t.setAge(24);

        Administrator ad = new Administrator();
        ad.setName("wangwu");
        ad.setAge(25);

        // 调用 register 方法
        register(s);
        register(t);
        register(ad);
    }

    // 定义一个方法, 表示注册, 要求这个方法可以接收管理员对象、教师对象和学生对象
    // 这就要求形参必须是三个类型的父类
    public static void register(Person p) {  // 当看见一个方法的形参是一个类, 那么可以给这个方法传递的实参除了这个类的对象, 还包括这个类的所有子类的对象
        p.show();  // 可以根据传递进来的不同对象, 执行不同的 show() 方法
    }
}

执行结果:

学生的信息为: zhangsan, 23
老师的信息为: lisi, 24
管理员的信息为: wangwu, 2

多态调用成员的规则:

调用成员变量: 编译看左边, 运行看左边.

调用成员方法: 编译看左边, 运行看右边.

程序示例:

public class Test {
    public static void main(String[] args) {
        // 用多态的方式创建对象
        Animal a = new Dog();  // 这是自动类型转换

        // 调用成员变量
        // javac 编译时看左边的父类中有没有这个变量, 如果有, 编译成功, 如果没有, 编译失败
        // java 运行时获取左边父类中成员变量的值
        System.out.println(a.name);  // Animal

        // 调用成员方法
        // javac 编译时看左边父类中有没有这个方法, 如果有, 编译成功, 如果没有, 编译失败
        // java 运行时实际运行的是子类中的方法
        a.show();  // 调用 Dog 类的 show() 方法. 

        // a 是 Animal 类型的, 所以用 a 调用成员变量或成员方法时, 默认从 Animal 这个类中去找. 
        // 用 a 调用成员变量时, 会把父类中的成员变量也继承下来, 父类里面有一个 name, 子类里面也有一个 name. 现在 a 是父类类型, 所以调用父类的 name
        // 用 a 调用成员方法时, 如果子类中对方法进行了重写, 那么在子类中会对父类方法进行覆盖, 所以实际调用的是子类中重写的方法. 
    }
}

class Animal {
    String name = "Animal";

    public void show() {
        System.out.println("调用 Animal 类的 show() 方法. ");
    }
}

class Dog extends Animal {
    String name = "Dog";

    @Override
    public void show() {
        System.out.println("调用 Dog 类的 show() 方法. ");
    }
}

class Cat extends Animal {
    String name = "cat";

    @Override
    public void show() {
        System.out.println("调用 Cat 类的 show() 方法. ");
    }
}

子类不重写父类的方法:

public class Test {
    public static void main(String[] args) {
        // 用多态的方式创建对象
        Animal a = new Dog();  // 这是自动类型转换
 
        // 调用成员变量
        // javac 编译时看左边的父类中有没有这个变量, 如果有, 编译成功, 如果没有, 编译失败
        // java 运行时获取左边父类中成员变量的值
        System.out.println(a.name);  // Animal
 
        // 调用成员方法
        // javac 编译时看左边父类中有没有这个方法, 如果有, 编译成功, 如果没有, 编译失败
        // java 运行时实际运行的是子类中的方法
        a.show();  // 调用 Animal 类的 show() 方法.  
 
        // a 是 Animal 类型的, 所以用 a 调用成员变量或成员方法时, 默认从 Animal 这个类中去找. 
        // 用 a 调用成员变量时, 会把父类中的成员变量也继承下来, 父类里面有一个 name, 子类里面也有一个 name. 现在 a 是父类类型, 所以调用父类的 name
        // 用 a 调用成员方法时, 如果子类中对方法进行了重写, 那么在子类中会对父类方法进行覆盖, 所以实际调用的是子类中重写的方法. 
    }
}
 
class Animal {
    String name = "Animal";
 
    public void show() {
        System.out.println("调用 Animal 类的 show() 方法. ");
    }
}
 
class Dog extends Animal {
    String name = "Dog";
 
    // @Override
    // public void show() {
    //     System.out.println("调用 Dog 类的 show() 方法. ");
    // }
}
 
class Cat extends Animal {
    String name = "cat";
 
    @Override
    public void show() {
        System.out.println("调用 Cat 类的 show() 方法. ");
    }
}

内存分析:


图2

第一步, 测试类的字节码文件进入方法区.

第二步, 虚拟机自动调用 main() 方法, 所以 main() 方法进栈. 下面开始执行 main() 方法里面的第一行代码.

第三步, 执行语句 Animal a = new Dog();, 将类的字节码文件加载到内存中的时候, 永远是先加载父类, 再加载子类. 此处 Dog 类的父类是 Animal, Animal 类的父类是 Object. 因此此处先加载 Object, 然后加载 Animal, 然后加载 Dog. 在方法区中, Dog 这个字节码文件和其父类 Animal 的字节码文件之间还有一个联系, 即 Dog 会记住 Animal 的字节码文件在方法区中的位置. 等号的左边在栈中开辟了一个小空间, 即变量 a, 类型为 Animal, 等号的右边有关键字 new, 因此在堆空间中开辟了一个小空间, 假设这个空间的地址值为 001, 这个空间会被分为两部分, 一部分存储从父类中继承下来的成员变量信息, 另一部分用来存储自己的成员变量信息. 初始化完成后会把地址值 001 赋给变量 a, a 就可以通过 001 找到堆空间中的这个对象. 到这里这一条语句才算执行完毕.

第四步, 执行语句 System.out.println(a.name); 时用 a 调用变量 name. 用多态的方式定义的变量, 在编译和运行时都是看左边, 即看父类. 此处 a 通过 001 去看堆空间中的那部分内存中, 用来存储从父类继承下来的成员变量的那部分空间, 看这部分空间中有没有要调用的这个成员变量, 此处为 name, 如果有 name, 那么编译成功, 如果没有则编译失败. 运行时还是去这部分空间找到这个成员变量, 获取它的值. 如果上一条语句为 Dog d = new Dog();, 则用 d 访问成员变量 name 时, 即执行 d.name 时, 不会去存放父类成员变量的那部分空间去找 name, 而是去存放子类自己的成员变量的那部分空间去找 name. 如果没找到, 才会去存放父类的成员变量的那部分空间去找 name.

第五步, 执行语句 a.show();, 用多态的方式定义的对象, 当这个对象调用方法时, 编译看左边, 即看父类, 执行看右边, 即看子类. 编译的时候, 会去方法区的父类的字节码文件中查看有没有 show() 方法, 如果找到了, 不会报错, 代码正常. 如果没有找到, 代码报错. 实际运行时, 会在方法区的子类的字节码文件中查找 show() 方法. 而子类可以重写这个 show() 方法去覆盖从父类中继承来的 show() 方法, 因此不同的对象执行的是各个不同子类的自己的方法.

多态的优势

在多态形式下, 右边对象可以实现解耦合, 便于扩展和维护.

Person p = new Student;
p.work();        // 业务逻辑发生改变时, 后续代码无需修改

假如写了上述代码之后, 过了一段时间, 要求不要让学生来工作了, 要换成老师来工作, 那么只需要改第一行代码为 Person p = new Teacher;, 其他代码不用修改.

定义方法的时候, 使用父类型作为参数, 可以接收所有子类对象, 体现多态的扩展性与便利.

定义集合时, 没有指明泛型, 则所有类型都能放到这个集合中.


图3 定义集合时没有指明泛型

多态的弊端

不能调用子类特有的方法, 即不存在于父类但是存在于子类的方法. 因为用多态的方式定义的对象, 在调用一个方法时, 编译时看父类, 运行时看子类, 而父类中没有这个方法, 则编译时报错.

程序示例:

public class Test {
    public static void main(String[] args) {
//        用多态的方式创建一个对象
        Animal a = new Dog();
//        调用子类重写了父类的方法
        a.eat();  // 狗吃骨头
//        调用父类的方法, 子类没有重写
        a.sleep();  // sleep
    }
}

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

    public void sleep() {
        System.out.println("sleep");
    }
}

class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("狗吃骨头");
    }

    //    子类特有的方法
    public void lookHome() {
        System.out.println("狗看家");
    }
}

class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }

    //    子类特有的方法
    public void catchMouse() {
        System.out.println("猫抓老鼠");
    }
}

尝试调用子类特有的方法, 发现没有:


图4

解决方案:

利用类型的强制转换, 将对象的类型由父类的类型转换为子类的类型. 父类的范围比子类的范围大, 就好像 int 的范围比 byte 的范围大.

程序示例:

Animal a = new Dog();
Dog d = (Dog) a;    // 这个 d 对象就可以执行 Dog 类有而 Animal 类没有的方法

关键字 instanceof 用于检查一个对象是否是一个类的实例. 用法: 对象名 instanceof 类名, 返回 boolean 值.

程序示例:

Animal a = new Dog();
if (a instanceof Dog) {
    Dog d = (Dog)a;
} else if (a instanceof Cat) {
    Cat c = (Cat) a;
} else {
    System.out.println("没有这个类型, 无法转换");
}

程序示例:

public class Test {
    public static void main(String[] args) {
        // 用多态的方式创建对象
        Animal a = new Dog();

        // Cat b = (Cat) a; // ClassCastException: class demo1.Dog cannot be cast to class demo1.Cat
        if (a instanceof Dog) {
            Dog d = (Dog) a;
        } else if (a instanceof Cat) {
            Cat c = (Cat) a;
        } else {
            System.out.println("没有这个类型, 无法转换");
        }
    }
}

class Animal {
    String name = "Animal";

    public void show() {
        System.out.println("调用 Animal 类的 show() 方法. ");
    }
}

class Dog extends Animal {
    String name = "Dog";

    @Override
    public void show() {
        System.out.println("调用 Dog 类的 show() 方法. ");
    }
}

class Cat extends Animal {
    String name = "cat";

    @Override
    public void show() {
        System.out.println("调用 Cat 类的 show() 方法. ");
    }
}

Java 14 开始添加了一个新特性, 可以将判断和强制转换合在一起写, 写成一行.

Animal a = new Dog();
// 先判断 a 是否为 Dog 类型, 如果是, 则将 a 强制类型转化为 Dog 类型并赋值给新建的一个变量 d, 如果不是则不强转
if (a instanceof Dog d) {
    d.lookHome();    // d 对象调用 Dog 类独有的方法 lookHome()
} else if (a instanceof Cat c) {

} else {
    System.out.println("没有这个类型, 无法转换");
}

练习:

根据需求完成代码:

1.定义狗类

属性: 年龄, 颜色

行为: eat(String something) 方法 (something 表示吃的东西)

行为: 看家 lookHome 方法 (无参数)

2.定义猫类

属性: 年龄, 颜色

行为: eat(String something) 方法 (something 表示吃的东西)

行为: 逮老鼠 catchMouse 方法 (无参数)

3.定义 Person 类饲养员

属性: 姓名, 年龄

行为: keepPet(Dog dog, String something) 方法, 功能: 喂养宠物狗, something 表示喂养的东西

行为: keepPet(Cat cat, String something) 方法, 功能: 喂养宠物猫, something 表示喂养的东西

生成空参有参构造, set 和 get 方法

4.定义测试类 (完成以下打印效果):

keepPet(Dog dog, String somethind) 方法打印内容如下:

年龄为 30 岁的老王养了一只黑颜色的 2 岁的狗

2 岁的黑颜色的狗两只前腿死死的抱住骨头猛吃

keepPet(Cat cat, String somethind) 方法打印内容如下:

年龄为 25 岁的老李养了一只灰颜色的 3 岁的猫

3 岁的灰颜色的猫眯着眼睛侧着头吃鱼

5.思考:
1.Dog 和 Cat 都是 Animal 的子类, 以上案例中针对不同的动物, 定义了不同的 keepPet 方法, 过于繁琐, 能否简化, 并体会简化后的好处?
2.Dog 和 Cat 虽然都是 Animal 的子类, 但是都有其特有方法, 能否想办法在 keepPet 中调用特有方法?

结构:


图5

代码:

Javabean 类:

public class Animal {
    private int age;
    private String color;

    public Animal() {
    }

    public Animal(int age, String color) {
        this.age = age;
        this.color = color;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public void eat(String something) {
        System.out.println("动物在吃" + something);
    }
}
public class Dog extends Animal {
    public Dog() {
    }

    public Dog(int age, String color) {
        super(age, color);
    }

    @Override
    public void eat(String something) {
        System.out.println(getAge() + " 岁的" + getColor() + "颜色的狗两只前腿死死的抱住" + something + "猛吃");
    }

    public void lookHome() {
        System.out.println("狗在看家");
    }
}
public class Cat extends Animal {
    public Cat() {
    }

    public Cat(int age, String color) {
        super(age, color);
    }

    @Override
    public void eat(String something) {
        System.out.println(getAge() + " 岁的" + getColor() + "颜色的猫眯着眼睛侧着头吃" + something);
    }

    public void catchMouse() {
        System.out.println("猫抓老鼠");
    }
}
public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void keepPet(Dog dog, String something) {
        System.out.println("年龄为 " + age + " 岁的" + name + "养了一只" + dog.getColor() + "颜色的 " + dog.getAge() + " 的狗.");
        dog.eat(something);
    }

    public void keepPet(Cat cat, String something) {
        System.out.println("年龄为 " + age + " 岁的" + name + "养了一只" + cat.getColor() + "颜色的 " + cat.getAge() + " 的猫.");
        cat.eat(something);
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        Person person1 = new Person("老王", 40);
        Dog dog = new Dog(2, "黑");
        person1.keepPet(dog, "骨头");

        Person person2 = new Person("老李", 54);
        Cat cat = new Cat(3, "灰");
        person2.keepPet(cat, "鱼");
    }
}

执行结果:

年龄为 40 岁的老王养了一只黑颜色的 2 的狗.
2 岁的黑颜色的狗两只前腿死死的抱住骨头猛吃
年龄为 54 岁的老李养了一只灰颜色的 3 的猫.
3 岁的灰颜色的猫眯着眼睛侧着头吃鱼

Person 类的改进:

public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    /*public void keepPet(Dog dog, String something) {
        System.out.println("年龄为 " + age + " 岁的" + name + "养了一只" + dog.getColor() + "颜色的 " + dog.getAge() + " 的狗.");
        dog.eat(something);
    }

    public void keepPet(Cat cat, String something) {
        System.out.println("年龄为 " + age + " 岁的" + name + "养了一只" + cat.getColor() + "颜色的 " + cat.getAge() + " 的猫.");
        cat.eat(something);
    }*/

    // 想要一个方法, 能接受所有的动物, 包括猫和狗
    // 方法的形参, 可以写这些类的父类 Animal
    public void keepPet(Animal animal, String something) {
        System.out.println("年龄为 " + age + " 岁的" + name + "养了一只" + animal.getColor() + "颜色的 " + animal.getAge() + " 的动物.");
        animal.eat(something);
    }
    // 弊端: 1. 不能完美描述当前是猫还是狗, 只能笼统地称为动物. 2. 不能调用某一种动物的特有的方法.
}

进一步改进:

public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    /*public void keepPet(Dog dog, String something) {
        System.out.println("年龄为 " + age + " 岁的" + name + "养了一只" + dog.getColor() + "颜色的 " + dog.getAge() + " 的狗.");
        dog.eat(something);
    }

    public void keepPet(Cat cat, String something) {
        System.out.println("年龄为 " + age + " 岁的" + name + "养了一只" + cat.getColor() + "颜色的 " + cat.getAge() + " 的猫.");
        cat.eat(something);
    }*/

    // 想要一个方法, 能接受所有的动物, 包括猫和狗
    // 方法的形参, 可以写这些类的父类 Animal
//    public void keepPet(Animal animal, String something) {
//        System.out.println("年龄为 " + age + " 岁的" + name + "养了一只" + animal.getColor() + "颜色的 " + animal.getAge() + " 的动物.");
//        animal.eat(something);
//    }
    // 弊端: 1. 不能完美描述当前是猫还是狗, 只能笼统地称为动物. 2. 不能调用某一种动物的特有的方法.

    // 进一步改进
    public void keepPet(Animal animal, String something) {
        if (animal instanceof Cat cat) {
            System.out.println("年龄为 " + age + " 岁的" + name + "养了一只" + cat.getColor() + "颜色的 " + cat.getAge() + " 的猫.");
            cat.eat(something);
        } else if (animal instanceof Dog dog) {
            System.out.println("年龄为 " + age + " 岁的" + name + "养了一只" + dog.getColor() + "颜色的 " + dog.getAge() + " 的狗.");
            dog.eat(something);
        } else {
            System.out.println("没有这种动物.");
        }
    }
}
posted @ 2024-09-08 22:42  有空  阅读(43)  评论(0)    收藏  举报