javaSE学习-面向对象9-三大特性之-多态

多态,是同一个对象,在不同时刻表现不同的行为

多态的前提:

  • 存在继承
  • 子类重写父类方法
  • 父类引用指向子类对象:父 f = 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
30
31
32
33
34
35
36
class Fu{
public int num = 10;
public void showMethod(){
System.out.println("show Fu");
}
public static void staticMethod(){
System.out.println("static Fu");
}
public void fuMethod(){
System.out.println("fu method");
}
}
class Zi extends Fu{
public int num = 100;
public int num2 = 200;
public void showMethod(){
System.out.println("show zi");
}
public void ziMethod(){
System.out.println("zi method");
}
public static void staticMethod(){
System.out.println("static zi");
}
}
public class TestDemo{
public static void main(String[] args) {
Fu fu = new Zi();
System.out.println(fu.num);
//System.out.println(fu.num2); //编译不通过
//fu.ziMethod(); //编译不通过,
fu.fuMethod();
fu.showMethod();
fu.staticMethod();
}
}

运行结果:

1
2
3
4
10
fu method
show zi
static Fu

在上面的程序中,使用多态,fu 加载变量num时,先查看父类中是否有这个变量,发现有 num ,则调用;调用子类特有变量 num2 和 ziMethod() 方法,因为 fu 是向上转型的,转到父类后查看没有变量 num2 和 ziMethod(),则编译不通过;加载父类特有的方法 fuMethod() 时,在子类中找不到,向上转型到父类中,查看有次方法,便调用父类的方法 fuMethod();加载子类重写父类的方法时,在子类中找到重写后的方法 showMethod(),则直接调用

总结

指向子类的父类引用变量,向上转型,所以只能调用父类中有的变量和方法,对于父类不存在子类中特有的方法,则不能访问调用;对于子类没有而父类中有的变量和方法,可以访问调用;如果子类中重写了父类中的方法,那么调用的是子类中重写后的方法

多态访问成员特点:

  • 构造方法:创建子类对象时访问父类构造方法,对父类数据进行初始化
  • 成员变量:编译看左边,运行看左边
  • 成员方法:编译看左边,运行看右边
  • 静态方法:编译看左边,运行看左边

多态的好处

  • 提高了代码的维护性
  • 提高了代码的扩展性

多态的弊端

只能使用父类的成员,而不能使用子类的特有功能

解决:向下转型,将父类的引用强制转换为子类的引用

Fu fu = new Fu();
Zi zi = (Zi)fu;

java 多态经典案例

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
public class A {
public String show(D obj) {
return ("A and D");
}

public String show(A obj) {
return ("A and A");
}
}

public class B extends A{
public String show(B obj){
return ("B and B");
}

public String show(A obj){
return ("B and A");
}
}

public class C extends B {}

public class D extends B {}

public class Test {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();

System.out.println("1--" + a1.show(b));
System.out.println("2--" + a1.show(c));
System.out.println("3--" + a1.show(d));
System.out.println("4--" + a2.show(b));
System.out.println("5--" + a2.show(c));
System.out.println("6--" + a2.show(d));
System.out.println("7--" + b.show(b));
System.out.println("8--" + b.show(c));
System.out.println("9--" + b.show(d));
}
}

运行结果:

1
2
3
4
5
6
7
8
9
1--A and A
2--A and A
3--A and D
4--B and A
5--B and A
6--A and D
7--B and B
8--B and B
9--A and D

在以上程序中,

结果分析

4的结果分析:

很多人以为会输出的是 “B and B”,调用的是B中找show(B)方法!

为什么没有直接在B中找show(B)方法调用呢,因为调用方法,首先看是谁调用的,a2的类型是A,而不是B,所以在查找方法时,先进入A类查找,而B中的方法 show(B),是B类中特有的方法,所以不会调用

  1. a2是A的引用,但是指向的是B类型
  2. a2调用方法 show(b),方法参数是B类型
  3. 查看引用类型A是否有父类,没有父类,然后在引用类型A中看是否有此方法
  4. A中没有参数为B类型的方法,然后找是否有参数为B的父类的方法
  5. B有父类A,找到方法 show(A)
  6. 然后查看a2的指向类型B中是否重写过该方法
  7. 在B中找到重写A的方法show(A),所以最后调用的是B中的方法,输出 “B and A ”
5的结果分析:
  1. a2是A的引用,但是指向的是B类型
  2. a2调用方法 show(c),方法参数是C类型;
  3. 查看引用类型A是否有父类,没有父类,然后在引用类型A中看是否有此方法
  4. A中没有参数为C类型的方法,然后找是否有参数为C的父类的方法
  5. C有父类B,没有参数为B类型的方法;B有父类A,有参数为A类型的方法
  6. 然后查看a2的指向类型B中是否重写过该方法
  7. 在B中找到重写A的方法show(A),所以最后调用的是B中的方法,输出 “B and A ”
修改程序
修改1

如果在上面的程序中加上A的父类:Fu ,添加方法show(C obj);其余不变:

1
2
3
4
5
6
7
8
class Fu {
public String show(C obj) {
return ("Fu and C");
}
}
class A extends Fu {
...
}

那么,5的结果会跟之前有所不同:

1
5--Fu and C
  1. a2 是 A 的引用,但是指向的是B类型
  2. a2 调用方法 show(c),方法参数是C类型;
  3. 在引用类型A中,查找方法 show(C) 是否存在;
  4. 找不到方法 show(C),查看引用类型A是否有父类
  5. 有父类Fu,然后在用用类型A的父类Fu中看是否有此方法
  6. Fu 中有参数为 C 的方法 show(C)
  7. 然后查看指向类型 B 中是否有重写后的方法
  8. B中没有重写方法 show(C),最终调用 Fu 类中的方法 show(C):输出 “ Fu and C ”
修改2

如果在上面的程序中加上A的父类:Fu ,添加方法show(C obj);其余不变:

1
2
3
4
5
6
7
8
9
10
11
class Fu {
public String show(C obj) {
return ("Fu and C");
}
}
class A extends Fu {
...
public String show(C obj) {
return ("A and C");
}
}

那么,5的结果会跟之前又有所不同:

1
5--A and C
  1. a2 是 A 的引用,但是指向的是B类型
  2. a2 调用方法 show(c),方法参数是C类型;
  3. 在引用类型A中,查找方法 show(C) 是否存在;
  4. 找到方法 show(C),然后查找A的子类:指向类型 B 中是否有重写 show(C) 的方法
  5. B中没有重写方法 show(C),调用 A 类中的方法 show(C):输出 “ A and C ”
修改3

如果在B中重写方法 show(C),那么结果又会不同:

1
2
3
public String show(C obj) {
return ("B and C");
}

输出 “ B and C ” !!!

在之前的分析中,5中查看 B 中是否有重写后的方法,之前的程序中B中不存在重写方法,现在B中加上重写方法 show(C),所以最终调用的是指向类型 B 的重写方法

可以得出:

在多态中,父类对象作为引用变量,子类对象作为被引用对象类型时,引用变量调用的方法必须是父类(引用对象)中定义过的方法,不能调用父类中没有而子类中有的方法!!!

调用的情况有以下几种:

  • 被调用方法仅在父类中被定义,而子类中没有被定义,那么调用的方法是父类中的方法
  • 被调用方法在父类中被定义,在子类中被重写,那么调用的是子类中重写后的方法

多态调用顺序

前提:

  • 被引用变量类型继承引用变量类型
  • 先看引用变量类型和被引用对象类型(指向类型)
  • 调用方法必须是引用变量类型对象中存在的方法

调用顺序:

this->引用类型,zi->被引用类型

this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)

在查找方法时,按这四个顺序查找,当某个查找到之后,还要进入被引用类型 zi 中查看是否有重写方法

  1. 进入 this 查找 show(O)
    • 有,进入被引用类型 zi 中,查找是否存在重写方法 show(O)
      • 有,调用 zi 的重写后的方法 show(O),结束
      • 没有,调用 this 中的方法 this.show(O),结束
    • 没有,执行步骤2
  2. 查找 this 是否有父类 super
    • 有,进入 super 中 查找是否存在方法 super.show(O)
      • 有,进入被引用类型 zi 中,查找是否存在重写方法 show(O)
        • 有,调用 zi 的重写后的方法 show(O),结束
        • 没有,调用 super 中的方法 super.show(O),结束
      • 没有,执行步骤3
    • 没有,执行步骤3
  3. 查找 this 中是否有 this.show((super)O),(方法参数是调用方法参数的父类的方法)
    • 有,进入被引用类型 zi 中,查找是否存在重写方法 show((super)O)
      • 有,调用 zi 的重写后的方法 show((super)O),结束
      • 没有,调用 this 中的方法 this.show((super)O),结束
    • 没有,执行步骤4,(如果是从2到3,那么不会出现这种情况)
  4. 查找 this 中是否有 super.show((super)O)
    • 有,进入被引用类型 zi 中,查找是否存在重写方法 show((super)O)
      • 有,调用 zi 的重写后的方法 show((super)O),结束
      • 没有,调用 super 中的方法 super.show((super)O),结束