Java多态

定义

对于同一个类型的不同实例对象,同一个行为具备不同的表现形式。

作用

  1. 消除同一类型之间的耦合关系;
  2. 使得不同对象对于同一行为具备多种表现形式;

原理

变量的静态类型 & 动态类型

变量的静态类型 = 引用类型 = 编译时变量 :不会被改变、在编译期可知
变量的动态类型 = 实例对象类型 = 运行时变量 :会变化、在运行期才可知

如下示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Test { 
// Human是 Man与Human的父类
static abstract class Human {
}

static class Man extends Human {
}

static class Woman extends Human {
}

// 执行代码
public static void main(String[] args) {

Human man = new Man();
// 变量man的静态类型 = 引用类型 = 编译时变量 = Human:不会被改变、在编译期可知
// 变量man的动态类型 = 实例对象类型 = 运行时变量 = Man :会变化、在运行期才可知

}
}

实现原理

通过将 子类对象实例 赋值给 父类引用变量使得编译时的静态变量 与 运行时的动态变量不一样,在调用方法 / 变量时,多态就发生了。

应用示例:

1
2
3
4
5
6
// 设 B类是A类的子类

A b = new B(); //编译时变量 = A b 、运行时变量 = new B()
b.name; // 调用了父类A的成员变量name
b.move(); // 调用的是子类B重写后的2个方法move()、content()
b.content();

结论:因将 子类对象引用 赋值给 父类对象变量,即A a = new B(),即 编译时变量和运行时变量不一样,所以多态发生

实现过程(直接指针访问)

  1. JVM 通过 引用类型(reference,即A的引用)查询Java栈中的本地变量表,得到堆中的对象类型的数据地址;
  2. 根据地址,从而找到方法区中的对象类型数据(B的对象类型数据);
  3. 查询对象类型数据中的方法表定位到实际类(B类)的方法,从而运行。

对于A a = new B()

数据存储方式

  • 对于 A a:作为引用类型数据,存储在JVM栈的本地变量表中;
  • 对于 new B()
    • 作为实例对象数据存储在堆中;
    • B的对象实例数据(接口、方法、对象类型等)的地址存储在堆中;
    • B的对象类型数据(对象实例数据的地址所执行的数据)存储在方法区中,在方法区中,对象类型数据中有1个指向该类方法的方法表;

引用类型访问实现方式

Java程序通过 栈上的引用类型数据(reference) 来访问Java堆上的对象。

由于引用类型数据(reference)在 Java虚拟机中只规定了一个指向对象的引用,但没定义该引用应该通过何种方式去定位、访问堆中的对象的具体位置所以对象访问方式 取决于 虚拟机实现。目前主流的对象访问方式有两种:

  • 句柄访问
  • 直接指针访问

对象访问方式

多态实现方式

方法重载(Overload)

定义

同一个类中具备多个重名的方法。

特点

  1. 方法名字必须相同;
  2. 参数列表必须不同:个数不同;个数不同但对应参数类型不同;
  3. 返回类型/访问修饰符可同可不同;
  4. 可声明新的或更广的检查异常;
  5. 能够在同一个类或子类中被重载;

应用场景

针对同一类的对象,对于不同条件同一行为的不同表现;

原理

静态分派,发生在编译阶段。

示例

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
package main.java.com.study.polymorphic;

/**
* @author: whb
* @description: 方法重载测试
*/
public class OverloadTest {
/**
* 类定义
*/
static abstract class Human {
}

/**
* 继承自抽象类Human
*/
static class Man extends Human {
}

static class Woman extends Man {
}

/**
* 定义的重载方法(方法名相同,但参数列表不同(此处是类型不同))
*
* @param guy
*/
public void sayHello(Human guy) {
System.out.println("hello,guy!");
}

public void sayHello(Man guy) {
System.out.println("hello gentleman!");
}

public void sayHello(Woman guy) {
System.out.println("hello lady!");
}

public static void main(String[] args) {
Human man = new Man();
Man woman = new Woman();
OverloadTest test = new OverloadTest();

test.sayHello(man);
test.sayHello(woman);
}
}

执行结果:

1
2
hello,guy!
hello gentleman!

结果解析:

  1. 方法重载(OverLoad)的原理 = 静态分派 = 根据 变量的静态类型 确定执行(重载)哪个方法;
  2. 所以上述的方法执行时,是根据变量(manwoman)的静态类型(HumanMan)确定重载sayHello()中参数为Human guyMan guy的方法,即sayHello(Human guy)sayHello(Man guy) ;

方法重写(Override)

定义

子类重写父类方法的实现。

特点

  1. 返回值、函数名、形参都不能改变,即外壳不变,重写内在实现;
  2. 不能被重写的方法:构造方法;不能继承/不具备访问权限的方法(如private);声明为finalstatic的方法;
  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
package main.java.com.study.polymorphic;

/**
* @author: whb
* @description: 方法重写测试类
*/
public class OverrideTest {
// 测试代码
public static void main(String[] args) {
// 情况1
Human man = new Man();
man.sayHello();

// 情况2
man = new Woman();
man.sayHello();
}

/**
* 定义父类
*/
static class Human {
public void sayHello() {
System.out.println("Human say hello");
}
}

/**
* 继承类Human 并 重写sayHello()
*/
static class Man extends Human {
@Override
public void sayHello() {
System.out.println("man say hello");
}
}

static class Woman extends Human {
@Override
public void sayHello() {
System.out.println("woman say hello");
}
}
}

执行结果

1
2
man say hello
woman say hello

结果分析

  1. 方法重写(Override) = 动态分派 = 根据 变量的动态类型 确定执行(重写)哪个方法;
  2. 对于情况1:根据变量(Man)的动态类型(man)确定调用man中的重写方法sayHello();
  3. 对于情况2:根据变量(Man)的动态类型(woman)确定调用woman中的重写方法sayHello();

方法重载、方法重写对比

方法重载、重写对比

本文标题:Java多态

文章作者:王洪博

发布时间:2018年04月04日 - 15:04

最后更新:2020年02月10日 - 03:02

原始链接:http://whb1990.github.io/posts/7b7480f6.html

▄︻┻═┳一如果你喜欢这篇文章,请点击下方"打赏"按钮请我喝杯 ☕
0%