🚀引言

在前两篇博客中,我们已经讲完了面向对象程序三大特性之一的封装、继承,
【Java 基础】类和对象(构造&this&封装&static&代码块)-CSDN博客
【Java 基础】三大特征之继承-CSDN博客
下面让我们来看看多态有哪些内容吧

1. 多态概念

💢💢在Java中,多态是面向对象编程中的一个重要概念,它允许不同类型的对象对同一方法进行不同的实现。具体来说,多态性指的是通过父类的引用变量来引用子类的对象,从而实现对不同对象的统一操作

  • 多态是方法或对象具有多种形态,是面向对象的第三大特征

  • 多态前提是两个对象(类)存在继承关系,多态是建立在封装和继承基础之上的。

2. 多态实现条件

在Java中,要实现多态性, 就必须满足以下条件:

  1. 继承关系
    存在继承关系的类之间才能够使用多态性。多态性通常通过一个父类用变量引用子类对象来实现。

  2. 方法重写
    子类必须重写(Override)父类的方法。通过在子类中重新定义和实现父类的方法,可以根据子类的特点行为改变这个方法的行为,如猫和狗吃东西的独特行为。

  3. 父类引用指向子类对象
    使用父类的引用变量来引用子类对象。这样可以实现对不同类型的对象的统一操作,而具体调用哪个子类的方法会在运行时多态决定
    例如,下面的案例是根据猫和狗吃东西动作的不同,而实现的多态:

 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
47
48
49
class Animal {

public void eat() {

System.out.println("动物吃东西");

}

}

class Dog extends Animal {

@Override

public void eat() {

System.out.println("狗吃狗粮");

}

}

class Cat extends Animal {

@Override

public void eat() {

System.out.println("猫吃猫粮");

}

}

public class Test {

public static void main(String[] args) {

Animal animal1 = new Dog();

Animal animal2 = new Cat();

animal1.eat();

animal2.eat();

}

}

重写(Override)是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。想要理解方法重写,需要知道以下概念:1. 继承关系:重写方法是基于父类和子类之间的继承关系。子类继承了父类的方法,包括方法的名称、参数列表和返回类型。2. 方法签名:重写的方法与父类的方法具有相同的方法签名,即方法的名称、参数列表和返回类型必须一致(当然,如果返回类型的对象本身的类型则可以不同,但是必须要有继承关系)。方法签名不包括方法体。3. @Override注解:为了明确表明这是一个重写的方法,可以使用@Override注解来标记子类中的方法。该注解会在编译时检查是否满足重写条件,如果不满足会报错。4. 动态绑定:通过父类引用变量调用被子类重写的方法时,会根据实际引用的对象类型,在运行时动态绑定到相应的子类方法。

 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
    class Person {
    
    public void mission() {
    
    System.out.println("人要好好活着!");
    
    }
    
    }
    
    class Student extends Person {
    
    @Override
    
    public void mission() {
    
    System.out.println("学生要好好学习!");
    
    }
    
    }
    
    public class DynamicBinding {
    
    public static void main(String[] args) {
    
    Person p1 = new Student();
    
    p1.mission();
    
    }
    
    }

重载与重写

重载是指在同一个类中,根据参数列表的不同,定义多个具有相同名称但参数不同的方法。重写则是指子类重新定义和实现了从父类继承的方法。

  • 重载

  • 定义:在同一个类中,根据参数列表的不同,定义多个具有相同名称但参数不同的方法。

  • 绑定方式:静态绑定,在编译时确定调用。

  • 用途:实现相似功能但具有不同参数的方法。

  • 重写

  • 定义:子类重新定义和实现了从父类继承的方法。

  • 绑定方式:动态绑定,在运行时确定调用。

  • 用途:改变父类方法的行为以适应子类的需求。

多态的转型

向上转型

向上转型是指父类的引用指向子类的对象。

  • 本质:父类的引用指向子类的对象。

  • 特点

    1. 编译类型看左边,运行类型看右边。
    2. 可以调用父类的所有成员(需遵守访问权限)。
    3. 不能调用子类的特有成员。
    4. 运行效果看子类的具体实现。
  • 语法:父类类型 对象名 = new 子类类型();

  • 样例代码

1
2
// 假设有一个父类Animal和一个子类Dog
Animal animal = new Dog();
 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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
class A1{

public String name;

public int age;

public A1(String name, int age) {

this.name = name;

this.age = age;

}

public void eat(){

System.out.println(this.name + " 父类");

}

public void bark1(){

System.out.println(this.name + " 不构成重写父类调用");

}

}

class A2 extends A1 {

private int num = 1;

public A2(String name, int age){

super(name,age);

}

public void eat(){

System.out.println(this.name + " 子类");

}

public void bark2(){

System.out.println(this.name + " 不构成重写子类调用");

}

}

public class Test {

public static void func1(A1 a) {

a.eat();

a.bark1();

}

public static A1 func2() {

A1 a = new A2("change",18);

return a;

}

public static void main(String[] args) {

A2 a2 = new A2("change", 18);

A1 a1 = a2;

A1 a = new A2("change", 18);

a.eat();

func1(a2);

func2();

}

}

4.2 向下转型

【优缺点】

  • 优点:让代码实现更简单灵活。

  • 缺陷:不能调用到子类特有的方法。

(1) 本质

一个已经向上转型的子类对象,将父类引用转为子类引用。

(2) 特点

aaaaaaa> 1. 只能强制转换父类的引用,不能强制转换父类的对象。 aaaaaaa> 2. 要求父类的引用必须指向的是当前目标类型的对象。 aaaaaaa> 3. 当向下转型后,可以调用子类类型中所有的成员。

(3) 语法

子类类型 引用名 = (子类类型) 父类引用;

(4) 样例代码

请参考具体编程语言的文档或示例来编写向下转型的样例代码。

 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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class Animal {

public void eat() {

System.out.println("Animal is eating.");

}

}

class Dog extends Animal {

@Override

public void eat() {

System.out.println("Dog is eating");

}

public void bark() {

System.out.println("Dog is barking");

}

}

class Cat extends Animal {

@Override

public void eat() {

System.out.println("Cat is eating");

}

public void bark() {

System.out.println("Cat is barking");

}

}

public class Test {

public static void main(String[] args) {

Animal animal = new Dog();

animal.eat();

Dog dog = (Dog) animal;

dog.bark();

}

}

向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。 比如我们在上面代码进行一些修改:

 解释:这段代码在运行时出现了 ClassCastException 类型转换异常原因是 Dog 类与 Cat 类 没有继承关系,因此所创建的是 Dog 类型对象在运行时不能转换成 Cat 类型对象。

4.3 instanceof 关键字

因此Java中为了避免上述类型转换异常的问题,提高向下转型的安全性,引入了 instanceof 比较操作符,用于**判断对象的类型是否为XX类型或XX类型的子类型,**如果该表达式为true,则可以安全转换。 

  • 格式:对象 instanceof 类名称
  • 解释:这将会得到一个boolean值结果,也就是判断前面的对象能不能当作后面类型的实例
  • 代码示例 :
 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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
class Animal {

public void eat() {

System.out.println("Animal is eating.");

}

}

class Dog extends Animal {

@Override

public void eat() {

System.out.println("Dog is eating");

}

public void bark() {

System.out.println("Dog is barking");

}

}

class Cat extends Animal {

@Override

public void eat() {

System.out.println("Cat is eating");

}

public void bark() {

System.out.println("Cat is barking");

}

}

public class Test {

public static void main(String[] args) {

Animal animal = new Dog();

if(animal instanceof Dog){

Dog dog = (Dog) animal;

dog.bark();

}

else if(animal instanceof Cat){

((Cat)animal).bark();

}

}

}

5. 多态的优缺点及应用

5.1 多态的优缺点

【使用多态的好处】

  1. **灵活性和可扩展性:**多态性使得代码具有更高的灵活性和可扩展性。通过使用父类类型的引用变量,可以以统一的方式处理不同类型的对象,无需针对每个具体的子类编写特定的代码。

  2. **代码复用:**多态性可以促进代码的复用。可以将通用的操作定义在父类中,然后由子类继承并重写这些操作。这样一来,多个子类可以共享相同的代码逻辑,减少了重复编写代码的工作量。

  3. **可替换性:**多态性允许将一个对象替换为其子类的对象,而不会影响程序的其他部分。这种可替换性使得系统更加灵活和可维护,可以方便地添加新的子类或修改现有的子类,而无需修改使用父类的代码。

  4. **代码扩展性:**通过引入新的子类,可以扩展现有的代码功能,而无需修改现有的代码。这种可扩展性使得系统在需求变化时更加容易适应和扩展。

【使用多态的缺陷】

  1. **运行时性能损失:**多态性需要在运行时进行方法的动态绑定,这会带来一定的性能损失。相比于直接调用具体的子类方法,多态性需要在运行时确定要调用的方法,导致额外的开销。
  2. **代码可读性下降:**多态性使得代码的行为变得更加动态和不确定。在某些情况下,可能需要跟踪代码中使用的对象类型和具体的方法实现,这可能降低代码的可读性和理解性。
  3. **限制访问子类特有成员:**通过父类类型的引用变量,只能访问父类及其继承的成员,无法直接访问子类特有的成员。如果需要访问子类特有的成员,就需要进行向下转型操作,这增加了代码的复杂性和维护的难度。

虽然多态性具有一些缺点,但在大多数情况下,其优点远远超过缺点,使得代码更具灵活性、可扩展性和可维护性。因此,多态性在Java编程中被广泛应用。

5.2 多态的应用

🥝多态数组

多态数组:数组的定义类型为父类类型,里面保存的实际元素类型为子类类型。
代码示例:(循环调用基类对象,访问不同派生类的方法

 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
class Animal {

public void eat() {

System.out.println("Animal is eating.");

}

}

class Dog extends Animal {

@Override

public void eat() {

System.out.println("Dog is eating");

}

}

class Cat extends Animal {

@Override

public void eat() {

System.out.println("Cat is eating");

}

}

public class Test{

public static void main(String[] args) {

Animal[] animals = {new Animal(),new Dog(),new Cat()};

for(Animal a: animals){a.eat();};

}

}

6. 注意事项

我们需要避免在构造方法中调用重写的方法,先来看一段代码:

 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
class B {

public B() {

func();

}

public void func() {

System.out.println("B.func()");

}

}

class D extends B {

private int num = 1;

@Override

public void func() {

System.out.println("D.func() " + num);

}

}

public class Test {

public static void main(String[] args) {

D d = new D();

}

}

代码运行结果分析

运行结果为:D.func 0,其原因如下:

  1. 构造过程:在构造D对象的同时,会调用B的构造方法。

  2. 方法调用:B的构造方法中调用了func方法,此时会触发动态绑定,会调用到D中的func方法。

  3. 变量状态:此时D对象自身还没有构造,因此num处在未初始化的状态,其值为0。如果具备多态性,num的值则应该是1。

  4. 构造函数内方法调用:所以在构造函数内,尽量避免使用实例方法,除了finalprivate方法。

结论

  • 简化对象状态:用尽量简单的方式使对象进入可工作状态。

  • 避免构造器中调用方法:尽量不要在构造器中调用方法(如果这个方法被子类重写,就会触发动态绑定,但是此时子类对象还没构造完成),可能会出现一些隐藏的但是又极难发现的问题。

结束语

💞 💞 💞 感谢阅读,希望这篇博客可以给你提供有益的参考和启示。祝大家天天开心!