Java是一门OOP(Object Oriented Programming)语言,OOP具备3个特征:继承,封装和多态.下面从JVM角度简单介绍一下多态的一些体现,重载和重写的实现原理.
重载
/**
* @Auther: xieyuhui
* @Date: 2019-02-02 10:25
* @Description: 重载
* sayHello()是一个重载方法,参数类型不同,
* 参数是一套继承体系,Man和Woman都继承至Human,
*/
public class StaticDispatch {
static abstract class Human {
}
static class Man extends Human {
}
static class Woman extends Human {
}
public void sayHello(Human human) {
System.out.println("hello,human");
}
public void sayHello(Man man) {
System.out.println("hello,man");
}
public void sayHello(Woman man) {
System.out.println("hello,Woman");
}
public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
StaticDispatch staticDispatch = new StaticDispatch();
staticDispatch.sayHello(man);
staticDispatch.sayHello(woman);
}
运行main函数将得到如下结果:
hello,human
hello,human
Process finished with exit code 0
疑问:为什么执行参数类型为Human的重载,而不是选择Man和Woman呢?
重写
/**
* @Auther: xieyuhui
* @Date: 2019-02-02 10:31
* @Description: 重写
*/
public class DynamicDispatch {
static abstract class Human {
protected abstract void sayHello();
}
static class Man extends DynamicDispatch.Human {
@Override
protected void sayHello() {
System.out.println("hello,man");
}
}
static class Woman extends DynamicDispatch.Human {
@Override
protected void sayHello() {
System.out.println("hello,woman");
}
}
public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
man.sayHello();
woman.sayHello();
man = new Woman();
man.sayHello();
}
}
运行main函数将得到如下结果:
hello,man
hello,woman
hello,woman
Process finished with exit code 0
疑问:疑问:为什么执行参数类型为Man和Woman的重写,而不是选择Human呢?
要想解释以上的问题,需要先理解JVM中分派的概念.
分派
首先定义几个重要的概念:
Human man = new Man();
Human woman = new Woman();
我们把上面的代码中Human称为变量的类型,这个类型是编译期可知的,所以称为静态类型(static Type),后面的Man称为变量的实际类型(Actual Type),实际类型只有在运行期才可以确定,编译器在编译阶段并不知道一个对象的实际类型是什么.
静态分派
在JVM中,重载是通过参数的静态类型作为判定依据的,所有依赖静态类型来定位方法执行版本的分派动作统称为静态分派,因为静态类型是编译期可知的,所有静态分派发生在编译期,重载就是典型的静态分派.
动态分派
在JVM中,重写会生成invokevirtual指令,执行该指令的第一步就是在运行期间确定接收者的实际类型,involvevirtual指令把常量池中的类方法符号引用解析到不同的直接引用上,这个过程就是重写的本质.我们把这种在运行期根据实际类型确定方法执行版本的分派过程称为动态分派.
单分派和多分派
方法的接收者和方法的参数统称为方法的宗量,根据分派基于多少宗量,可以将分派划分为单分派和多分派两种,单分派指方法根据一个宗量进行选择,多分派指方法根据多个宗量进行选择.看下面的代码:
/**
* @Auther: xieyuhui
* @Date: 2019-02-02 15:34
* @Description: 单分派与多分派演示
* 这里有两套继承体系,一个QQ和360,它们做为参数用来解释静态分派
* 一个是Father和Son,它们做为类型用来解释动态分派
*/
public class Dispatch {
static class QQ {
}
static class QQSon extends QQ {
}
static class _360 {
}
static class _360Son extends _360 {
}
public static class Father {
public void hardChoice(QQ arg) {
System.out.println("father choose qq");
}
public void hardChoice(QQSon arg) {
System.out.println("father choose qqSon");
}
public void hardChoice(_360 arg) {
System.out.println("father choose 360");
}
public void hardChoice(_360Son arg) {
System.out.println("father choose 360Son");
}
}
public static class Son extends Father {
@Override
public void hardChoice(QQ arg) {
System.out.println("son choose qq");
}
@Override
public void hardChoice(QQSon arg) {
System.out.println("son choose qqSon");
}
@Override
public void hardChoice(_360 arg) {
System.out.println("son choose 360");
}
@Override
public void hardChoice(_360Son arg) {
System.out.println("son choose 360Son");
}
}
public static void main(String[] args) {
Father father = new Father();
Father son = new Son();
QQ qq = new QQ();
QQ qqSon = new QQSon();
_360 _360 = new _360();
_360 _360Son = new _360Son();
father.hardChoice(qq);
father.hardChoice(qqSon);
son.hardChoice(_360);
son.hardChoice(_360Son);
}
}
运行main函数得到如下结果:
father choose qq
father choose qq
son choose 360
son choose 360
Process finished with exit code 0
上面的代码结合使用了重载和重写,Father和Son中分别都有重载方法,而Son又重写了Father的方法.结合JVM分派的概念和过程,可以推导出main函数的结果.
首先看下静态分派的过程.也就是重载的选择过程.这时选择目标方法的依据有两点:
- 静态类型是Father还是Son.
- 方法参数是QQ还是_360.
方法重载需要考虑对象的静态类型和参数类型,所以Java语言的静态分派属于多分派类型.
再来看动态分派的过程,也就是重写的选择过程.在运行期间,不管是对象的静态类型或是参数的静态类型已经确定了,此时唯一可以影响JVM做选择的就是对象的实际类型是Father还是Son.
方法重写需要考虑的是对象的实际接受者.因为只有一个依据,所以Java语言的动态分派属于单分派.
总结
- 重载和重写是多态的实现手段.
- 重载通过静态类型作为依据.
- 重写通过实际类型作为依据.
- Java在1.8之前是一门静态多分派,动态单分派的语言.