多态

多态

  • 多态允许不同类的对象对同一消息做出不同的响应,即同一操作作用于不同的对象上会产生不同的行为。
  • 多态通过抽象和接口来实现,它提高了代码的灵活性和可扩展性。
  • 多态使得代码更具有通用性和可复用性,因为一个方法可以接受多种不同类型的参数。

概念:是指同一行为,具有多个不同表现形式。

代码当中体现多态性,其实就是一句话:父类引用指向子类对象。

封装继承多态的关系

  • 封装是指封装成抽象的类,并且对于可信的类或者对象,是可以操作的,对于不可信的进⾏隐藏。
  • 继承是指可以使⽤现有类的所有功能,而且还可以在现有功能的基础上做拓展。
  • 多态是基于继承的,他是指父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或者表现出不同的行为,使得同⼀个属性在⽗类及其⼦类中具有不同的含义。
  • 重载就是多态的⼀个例⼦,是编译时的多态。其实我们所说的多态是运⾏时多态,也就是说编译的时候不确定调⽤哪个具体⽅法,⼀直延迟到运行时才可以确定,所以多态又叫延迟方法。
image-20240224005703967

格式:

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(); // 子类没有,父类有,向上找到父类

// 编译看左边,左边是Fu,Fu当中没有methodZi方法,所以编译报错。
//obj.methodZi(); // 错误写法!父类不能用子类独有的方法

}

}

访问成员变量的两种方式:

直接通过对象名称访问成员变量:看等号左边是谁,优先用谁,没有则向上找。

间接通过成员方法访问成员变量:看该方法属于谁,优先用谁,没有则向上找。

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); // 父:10,成员变量优先使用左边
//System.out.println(obj.age); // 父类没有,错误写法!

// 子类没有覆盖重写,就是父:10
// 子类如果覆盖重写,就是子:20
obj.showNum(); //20,子类重写了方法,方法属于谁优先用谁
}
}

多态的好处

image-20240224004446593

多态的上下转型

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(); //动物睡觉
//animal.catchfish(); 无法访问
System.out.println(animal.age); //10


Cat cat = (Cat) animal; //向下转型
System.out.println("=======向下转型=======");
cat.eat(); //猫吃鱼
cat.catchfish(); //猫抓鱼
cat.sleep(); //动物睡觉
System.out.println(cat.age); //3


image-20240224004925127

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代表的是一只猫
animal.eat(); // 猫吃鱼

//animal.catchMouse(); //错误写法,调用子类特有方法要向下转型
// 向下转型,进行“还原”动作
Cat cat = (Cat) animal;
cat.catchMouse(); // 猫抓老鼠

// 下面是错误的向下转型
// 本来new的时候是一只猫,现在非要当做狗
// 错误写法!编译不会报错,但是运行会出现异常:
// java.lang.ClassCastException,类转换异常
Dog dog = (Dog) animal;
}

如何才能知道一个父类引用的对象,本来是什么子类?

1
对象 instanceof 类名称

这将会得到一个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(); // 狗吃SHIT
// 如果希望用子类特有方法,需要向下转型

// 判断一下父类引用animal本来是不是Dog
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.watchHouse();
}

// 判断一下animal本来是不是Cat
if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.catchMouse();
}
}

多态
http://example.com/2023/05/13/Java/Java基础/多态/
作者
PALE13
发布于
2023年5月13日
许可协议