【深入Java虚拟机】之五:多态性实现机制——静态分派与动态分派
方法解析
Class文件的编译过程中不包含传统编译中的连接步骤,一切方法调用在Class文件里面存储的都只是符号引用,而不是方法在实际运行时内存布局中的入口地址。这个特性给Java带来了更强大的动态扩展能力,使得可以在类运行期间才能确定某些目标方法的直接引用,称为动态连接,也有一部分方法的符号引用在类加载阶段或第一次使用时转化为直接引用,这种转化称为静态解析。这在前面的“Java内存区域与内存溢出”一文中有提到。
静态解析成立的前提是:方法在程序真正执行前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的。换句话说,调用目标在编译器进行编译时就必须确定下来,这类方法的调用称为解析。
在Java语言中,符合“编译器可知,运行期不可变”这个要求的方法主要有静态方法和私有方法两大类,前者与类型直接关联,后者在外部不可被访问,这两种方法都不可能通过继承或别的方式重写出其他的版本,因此它们都适合在类加载阶段进行解析。
Java虚拟机里共提供了四条方法调用字节指令,分别是:
- invokestatic:调用静态方法。
- invokespecial:调用实例构造器<init>方法、私有方法和父类方法。
- invokevirtual:调用所有的虚方法。
- invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象。
静态分派
所有依赖静态类型来定位方法执行版本的分派动作,都称为静态分派,静态分派的最典型应用就是多态性中的方法重载。静态分派发生在编译阶段,因此确定静态分配的动作实际上不是由虚拟机来执行的。下面通过一段方法重载的示例程序来更清晰地说明这种分派机制:
class Human{
}
class Man extends Human{
}
class Woman extends Human{
}
public class StaticPai{
public void say(Human hum){
System.out.println("I am human");
}
public void say(Man hum){
System.out.println("I am man");
}
public void say(Woman hum){
System.out.println("I am woman");
}
public static void main(String[] args){
Human man = new Man();
Human woman = new Woman();
StaticPai sp = new StaticPai();
sp.say(man);
sp.say(woman);
}
}
上面代码的执行结果如下:
I am human
I am human
以上结果的得出应该不难分析。在分析为什么会选择参数类型为Human的重载方法去执行之前,先看如下代码:
回到上面的代码分析中,在调用say()方法时,方法的调用者(回忆上面关于宗量的定义,方法的调用者属于宗量)都为sp的前提下,使用哪个重载版本,完全取决于传入参数的数量和数据类型(方法的参数也是数据宗量)。代码中刻意定义了两个静态类型相同、实际类型不同的变量,可见编译器(不是虚拟机,因为如果是根据静态类型做出的判断,那么在编译期就确定了)在重载时是通过参数的静态类型而不是实际类型作为判定依据的。并且静态类型是编译期可知的,所以在编译阶段,Javac编译器就根据参数的静态类型决定使用哪个重载版本。这就是静态分派最典型的应用。
动态分派
动态分派与多态性的另一个重要体现——方法覆写有着很紧密的关系。向上转型后调用子类覆写的方法便是一个很好地说明动态分派的例子。这种情况很常见,因此这里不再用示例程序进行分析。很显然,在判断执行父类中的方法还是子类中覆盖的方法时,如果用静态类型来判断,那么无论怎么进行向上转型,都只会调用父类中的方法,但实际情况是,根据对父类实例化的子类的不同,调用的是不同子类中覆写的方法,很明显,这里是要根据变量的实际类型来分派方法的执行版本的。而实际类型的确定需要在程序运行时才能确定下来,这种在运行期根据实际类型确定方法执行版本的分派过程称为动态分派。
单分派和多分派
前面给出:方法的接受者(亦即方法的调用者)与方法的参数统称为方法的宗量。但分派是根据一个宗量对目标方法进行选择,多分派是根据多于一个宗量对目标方法进行选择。
class Eat{
}
class Drink{
}
class Father{
public void doSomething(Eat arg){
System.out.println("爸爸在吃饭");
}
public void doSomething(Drink arg){
System.out.println("爸爸在喝水");
}
}
class Child extends Father{
public void doSomething(Eat arg){
System.out.println("儿子在吃饭");
}
public void doSomething(Drink arg){
System.out.println("儿子在喝水");
}
}
public class SingleDoublePai{
public static void main(String[] args){
Father father = new Father();
Father child = new Child();
father.doSomething(new Eat());
child.doSomething(new Drink());
}
}
运行结果应该很容易预测到,如下:
儿子在喝水
我们首先来看编译阶段编译器的选择过程,即静态分派过程。这时候选择目标方法的依据有两点:一是方法的接受者(即调用者)的静态类型是Father还是Child,二是方法参数类型是Eat还是Drink。因为是根据两个宗量进行选择,所以Java语言的静态分派属于多分派类型。
再来看运行阶段虚拟机的选择,即动态分派过程。由于编译期已经了确定了目标方法的参数类型(编译期根据参数的静态类型进行静态分派),因此唯一可以影响到虚拟机选择的因素只有此方法的接受者的实际类型是Father还是Child。因为只有一个宗量作为选择依据,所以Java语言的动态分派属于单分派类型。
【深入Java虚拟机】之五:多态性实现机制——静态分派与动态分派的更多相关文章
- 深入Java虚拟机:多态性实现机制——静态分派与动态分派
方法解析 Class文件的编译过程中不包含传统编译中的连接步骤,一切方法调用在Class文件里面存储的都只是符号引用,而不是方法在实际运行时内存布局中的入口地址.这个特性给Java带来了更强大的动态扩 ...
- 《深入理解 Java 虚拟机》学习 -- 类加载机制
<深入理解 Java 虚拟机>学习 -- 类加载机制 1. 概述 虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的 J ...
- 【Java】实战Java虚拟机之五“开启JIT编译”
今天开始实战Java虚拟机之五“开启JIT编译” 总计有5个系列 实战Java虚拟机之一“堆溢出处理” 实战Java虚拟机之二“虚拟机的工作模式” 实战Java虚拟机之三“G1的新生代GC” 实战Ja ...
- 简单介绍Java的静态分派和动态分派
最近复习JVM的知识,对于静态分派和动态分派的理解有点混乱,于是自己尝试写写代码,在分析中巩固知识. 有如下一段代码,请问每一段分别输出什么? package com.khlin.my.test; c ...
- 转:【深入Java虚拟机】之五:多态性实现机制——静态分派与动态分派
转载请注明出处:http://blog.csdn.net/ns_code/article/details/17965867 方法解析 Class文件的编译过程中不包含传统编译中的连接步骤,一切方法 ...
- java虚拟机(一)——内存管理机制与OOM异常
一 java内存区域与内存溢出异常(OOM) 1)运行时数据区域划分 1.程序计数器(Program Conuter Register) 程序计数器是一块较小的内存空间,它是当前线程执 ...
- 深入java虚拟机学习 -- 内存管理机制
前面说过了类的加载机制,里面讲到了类的初始化中时用到了一部分内存管理的知识,这里让我们来看下Java虚拟机是如何管理内存的. 先让我们来看张图 有些文章中对线程隔离区还称之为线程独占区,其实是一个意思 ...
- Java虚拟机的类载入机制
Java虚拟机类载入过程是把Class类文件载入到内存.并对Class文件里的数据进行校验.转换解析和初始化,终于形成能够被虚拟机直接使用的java类型的过程. 在载入阶段,java虚拟机须要完毕下面 ...
- 通过字节码分析Java方法的静态分派与动态分派机制
在上一次[https://www.cnblogs.com/webor2006/p/9723289.html]中已经对Java方法的静态分派在字节码中的表现了,也就是方法重载其实是一种静态分派的体现,这 ...
随机推荐
- Java输入几行字符串
查找书籍 给定n本书的名称和定价,本题要求编写程序,查找并输出其中定价最高和最低的书的名称和定价. 输入格式: 输入第一行给出正整数n(<10),随后给出n本书的信息.每本书在一行中给出书名,即 ...
- 优化mysql查询
mysql提供了一个特别的explain语句,用来分析查询语句的性能 : explain select ... 1.在所有用于where,order by,group by的列上添加索引 创建索引 添 ...
- UNIX环境C语言进程控制
一.进程ID 进程ID即是进程标识,每一个进程都会有一个唯一的非负整数来作为它的进程ID. ID为0的进程通常是调度进程,也可称为交换进程,该进程是内核的一部分,不执行硬盘上的程序,因此也被称为系统进 ...
- Re:从零开始的Linux之路(文件权限)
基于 Red Hat Enterprise Linux 7.5 或者 CentOS 7.4 基本概念 Linux最核心的一个概念就是:Linux里面任何东西都可以被视为一个文件,包括系统本身(说到底L ...
- python--MySQL权限管理 数据备份还原
一 权限管理 mysql最高管理者是root用户, 这个一般掌握在公司DBA手里, 当你想去对数据库进行一些操作的时候,需要DBA授权给你. 1. 对新用户增删改 1. 创建用户 # 要先use my ...
- Django ORM操作及进阶
一般操作 看专业的官网文档,做专业的程序员! 必知必会13条 <1> all(): 查询所有结果 <2> filter(**kwargs): 它包含了与所给筛选条件相匹配的对象 ...
- 剑指Offer(书):二叉树的镜像
题目:操作给定的二叉树,将其变换为源二叉树的镜像. public void Mirror(TreeNode root) { if (root == null) { return ; } if (roo ...
- 关于EF使用脏读(连接会话开始执行设置隔离级别)
SQL Server中事物隔离级别Read Uncommitted和with(nolock) 注意:应该使用后者(修改后的版本):
- Selenium启动项参数设置
再Selenium中使用不同的Webdriver可能会有不一样的方法,有些相同的操作会得到不同的结果, 本文主要介绍的是Chrome()的使用方法. 其他的Webdriver可以参考官方文档 Chro ...
- python---类接口技术
类接口技术 扩展只是一种同超类接口的方式.下面所展示的sPecial'ze.Py文件定义了多个类,示范了一些常用技巧.Super 定义一个method函数以及一个delegate函数.Inherito ...