一、什么是多态性
多态性是允许你将父对象设置成为一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作 —— Charlie Calverts。
Java中,继承(extends)和实现(implements)是多态的前提。
class Fu {
public void method() {
System.out.println("父类方法");
}
public void methodFu() {
System.out.println("父类专属方法");
}
}
class Zi extends Fu {
@Override
public void method() {
System.out.println("子类方法");
}
}
public class Test {
public static void main(String[] args) {
// 多态写法:左侧父类引用`指向`右侧子类对象
Fu obj = new Zi();
obj.method(); // 子类方法:`new谁运行谁`
obj.methodFu(); // 父类专属方法:子类中没有对应方法才去父类中找
}
}
1. 父类引用指向继承类的对象
编写格式:[父类名称] [对象名] = new [继承类名称]();
2. 接口引用指向实现类的对象
编写格式:[接口名称] [对象名] = new [实现类名称]();
二、成员变量访问
- 直接通过对象名称访问成员变量:看等号左边是谁,优先用谁,没有则向上找
- 间接通过成员方法访问成员变量:看该方法属于谁,优先用谁,没有则向上找
class Fu {
int num = 10;
public void showNum() {
System.out.println(num);
}
}
class Zi extends Fu {
int num = 20;
@Override
public void showNum() {
System.out.println(num);
}
}
public class DemoMultiField {
public static void main(String[] args) {
// 多态写法,父类引用指向子类对象
Fu obj = new Zi(); // 10
System.out.println(obj.num);
// 子类没有覆盖重写:看父类,10
// 子类重写了:看子类,20
obj.showNum();
}
}
速记口诀:编译看左边,运行还看左边
三、成员方法调用
new的是谁就优先用谁,没有则向上找
public class Fu {
public void method() {
System.out.println("父类方法");
}
public void methodFu() {
System.out.println("父类特有方法");
}
}
public class Zi extends Fu {
@Override
public void method() {
System.out.println("子类方法");
}
public void methodZi() {
System.out.println("子类特有方法");
}
}
public class DemoMultiMethod {
public static void main(String[] args) {
Fu obj = new Zi();
obj.method(); // 父子都有,优先用子(因为new的是子)
obj.methodFu(); // 子类没有父类有,向上找到父类
// 编译看左:左边是Fu,Fu中没有methodZi(),所以编译报错
// 运行看右:右边是Zi,Zi中有method()就运行method(),Zi中没有methodFu()就去父类中找methodFu()
// obj.methodZi(); // 异常
}
}
速记口诀:编译看左边,运行看右边
四、多态的优势
五、可能导致的问题
1. 向上转型与向下转型
有以下类定义代码:
public abstract class Animal {
public abstract void eat();
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
public void catchMouse() {
// 子类特有方法
System.out.println("猫抓老鼠");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("够吃SHIT");
}
public void watchHouse() {
System.out.println("狗看家");
}
}
向上转型一定是安全的,但是也存在弊端:对象一旦向上转型为父类,就无法调用子类特有的内容
解决方案:使用向下转型还原,将父类还原为子类
请注意:只有对象`本来是猫,才能还原成猫`,否则JVM会抛出类转换异常 java.lang.ClassCastException
案例:失败的向下转型
public class Demo01ClassCast {
public static void main(String[] args) {
// 对象的向上转型,就是父类引用指向子类对象
Animal animal = new Cat(); // 本来创建的是一只猫,猫向上转型为动物
animal.eat();
// animal.catchMouse(); // 错误写法!Animal类没有这方法
// 正确的向下转型,进行`还原`动作
Cat cat = (Cat) animal;
cat.catchMouse(); // 原本是猫,成功还原
// 下面是错误的向下转型,原本new的是猫,非要转成狗
// 编译时不会报错,但是运行时会抛出异常:java.lang.ClassCastException 类转换异常
Dog dog = (Dog) animal;
dog.watchHouse();
}
}
2. 避免类转换异常的方式
如何才能知道一个父类引用的对象,本来是什么子类?使用 instanceof
编写格式:[对象名] instanceof [类名]
表达式返回 boolean 结果,表示判断前面的对象能否作为后面类型的实例
应用案例:协同开发时的继承类/实现类判断
public class Demo02InstanceOf {
public static void main(String[] args) {
giveMeAPet(new Dog());
giveMeAPet(new Cat());
}
public static void giveMeAPet(Animal animal) {
// 在实际开发类似的方法中,我们无法确定此时的animal究竟是哪个子类的实例化对象
// 此时使用 instanceof 就显得十分必要
// 如果希望调用子类特有方法,需要向下转型
if (animal instanceof Dog) { // 判断animal是否是一个狗类的实例?
Dog dog = (Dog) animal;
dog.watchHouse(); // 狗看家
}
if (animal instanceof Cat) { // 判断animal是否是一个猫类的实例?
Cat cat = (Cat) animal;
cat.catchMouse(); // 猫抓老鼠
}
}
}
图片内容源自B站黑马Java基础公开课