多态
- 多态允许不同类的对象对同一消息做出不同的响应,即同一操作作用于不同的对象上会产生不同的行为。
- 多态通过抽象和接口来实现,它提高了代码的灵活性和可扩展性。
- 多态使得代码更具有通用性和可复用性,因为一个方法可以接受多种不同类型的参数。
概念:是指同一行为,具有多个不同表现形式。
代码当中体现多态性,其实就是一句话:父类引用指向子类对象。
封装继承多态的关系
- 封装是指封装成抽象的类,并且对于可信的类或者对象,是可以操作的,对于不可信的进⾏隐藏。
- 继承是指可以使⽤现有类的所有功能,而且还可以在现有功能的基础上做拓展。
- 多态是基于继承的,他是指父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或者表现出不同的行为,使得同⼀个属性在⽗类及其⼦类中具有不同的含义。
- 重载就是多态的⼀个例⼦,是编译时的多态。其实我们所说的多态是运⾏时多态,也就是说编译的时候不确定调⽤哪个具体⽅法,⼀直延迟到运行时才可以确定,所以多态又叫延迟方法。
格式:
1 2
| 父类名称 对象名 = new 子类名称(); 接口名称 对象名 = new 实现类名称();
|
多态的访问规则
成员变量:在多态中,成员变量的访问规则是不会发生多态的,即使用的是编译时类型的变量。无论实际运行时对象是什么类型,访问的都是编译时类型定义的变量。编译看左边,运行看左边。
成员方法:在多态中,成员方法的访问规则是发生多态的,即使用的是运行时类型的方法。实际调用的方法是对象的实际类型定义的方法。编译看左边,运行看右边
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| 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 class Demo02MultiMethod { public static void main(String[] args) { Fu obj = new Zi(); obj.method(); obj.methodFu();
}
}
|
访问成员变量的两种方式:
直接通过对象名称访问成员变量:看等号左边是谁,优先用谁,没有则向上找。
间接通过成员方法访问成员变量:看该方法属于谁,优先用谁,没有则向上找。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| public class Fu { int num = 10; public void showNum() { System.out.println(num); }
public void method() { System.out.println("父类方法"); }
public void methodFu() { System.out.println("父类特有方法"); } }
public class Zi extends Fu { int num = 20; int age = 16;
@Override public void showNum() { System.out.println(num); } @Override public void method() { System.out.println("子类方法"); } public void methodZi() { System.out.println("子类特有方法"); }
}
public class Demo01MultiField { public static void main(String[] args) { Fu obj = new Zi(); System.out.println(obj.num);
obj.showNum(); } }
|
多态的好处
多态的上下转型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| class Animal { public int age = 10; public void eat() { System.out.println("动物吃食物"); } public void sleep() { System.out.println("动物睡觉"); } }
class Cat extends Animal{ public int age = 3; public void eat() { System.out.println("猫吃鱼"); } public void catchfish() { System.out.println("猫抓鱼"); } }
Animal animal = new Cat(); System.out.println("=======向上转型======="); animal.eat(); animal.sleep(); System.out.println(animal.age);
Cat cat = (Cat) animal; System.out.println("=======向下转型======="); cat.eat(); cat.catchfish(); cat.sleep(); System.out.println(cat.age);
|
new Cat() 在堆中创建了一个 Cat 对象,其中包含父类 Animal 的属性和方法(黄色部分)以及子类 Cat 的属性和方法(灰色部分)
- 用父类的引用 animal 指向该对象,此时发生了向上转型,引用 animal 实际上指向的是堆中属于父类 Animal 的那一部分(即黄色的那一部分),所以此时 animal.age = 10。
- 但由于 eat() 被重写了,所以会执行 Cat 类中的 eat() 方法,输出“猫吃鱼”。黄色部分中的 eat 用红色标注表示被重写/覆盖。
- animal.catchfish() 会报错,因为访问不到catchfish() 方法。
- 再使用 Cat cat = (Cat) animal 进行向下转型,就恢复到了正常的继承情况,此时 animal.age = 3。
- eat() 被重写,输出“猫吃鱼”,可正常访问 catchfish() 方法,也可正常访问继承来的 sleep() 方法。
多态向下转型调用子类特有方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| public abstract class Animal { public abstract void eat(); }
public class Cat extends Animal { @Override public void eat() { System.out.println("猫吃鱼"); } public void catchMouse() { System.out.println("猫抓老鼠"); } }
public class Dog extends Animal { @Override public void eat() { System.out.println("狗吃SHIT"); } public void watchHouse() { System.out.println("狗看家"); } }
public static void main(String[] args) { Animal animal = new Cat(); animal.eat();
Cat cat = (Cat) animal; cat.catchMouse();
Dog dog = (Dog) animal; }
|
如何才能知道一个父类引用的对象,本来是什么子类?
这将会得到一个boolean值结果,也就是判断前面的对象能不能当做后面类型的实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public static void main(String[] args) { Animal animal = new Dog(); animal.eat(); if (animal instanceof Dog) { Dog dog = (Dog) animal; dog.watchHouse(); }
if (animal instanceof Cat) { Cat cat = (Cat) animal; cat.catchMouse(); } }
|