多态
多态: polymorphism
概述
有了封装才有面向对象, 有了面向对象才有继承和多态.
什么是多态: 同类型的对象, 表现出的不同形态.

多态的表现形式: 父类类型 对象名称 = 子类对象;
多态的前提:
-
有继承或实现关系 (实现与接口有关)
-
有父类引用指向子类对象,
Fu f = new Zi();
-
要有方法重写.
程序示例:
父类:
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() 方法. "); } }
内存分析:

第一步, 测试类的字节码文件进入方法区.
第二步, 虚拟机自动调用 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;
, 其他代码不用修改.
定义方法的时候, 使用父类型作为参数, 可以接收所有子类对象, 体现多态的扩展性与便利.
定义集合时, 没有指明泛型, 则所有类型都能放到这个集合中.

多态的弊端
不能调用子类特有的方法, 即不存在于父类但是存在于子类的方法. 因为用多态的方式定义的对象, 在调用一个方法时, 编译时看父类, 运行时看子类, 而父类中没有这个方法, 则编译时报错.
程序示例:
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("猫抓老鼠"); } }
尝试调用子类特有的方法, 发现没有:

解决方案:
利用类型的强制转换, 将对象的类型由父类的类型转换为子类的类型. 父类的范围比子类的范围大, 就好像 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 中调用特有方法?
结构:

代码:
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("没有这种动物."); } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术