在我们平时的工作学习中写java代码时,如果我们在同一个类中定义了两个方法名和参数类型都相同的方法时,编译器会直接报错给我们。还有在代码运行的时候,如果子类定义了一个与父类完全相同的方法的时候,父类的方法就会被覆盖,(也就是我们平时说的重写)。那么,jvm虚拟机是如何精确识别目标方法的。

重载、重写与多态

重载:方法名相同而参数类型不相同的方法之间的关系。

重写:方法名相同并且参数类型也相同的方法之间的关系。

这两个概念我们耳熟能详,那么重载和重写是如何判断的呢?

重载:

重载的方法在编译期间就可以完成识别。java编译器会根据所传入参数的声明类型对方法名相同的方法进行选取。

除了同一个类中,如果A继承了B,A中定义了与B中的非私有方法同名的方法,而且这两个方法的参数类型不同,那么A和B类同样构成了重载

重写:

重写方法的判断是在运行期间才可以完成识别的。

我们都知道多态是java面向对象语言的三大特性之一。而方法的重写,就是最能体现多态的一种方式:它允许子类在继承父类部分特性的同时,拥有自己独特的行为。

举个简单的例子帮大家理解一下多态:

比如我们按下b这个键,在dota中代表的是敌法师的blink技能,在lol中是回城,在网游里又成了背包。对于不同的对象拥有不同的行为,这就是多态。

静态绑定与动态绑定

接下来,我们来看一下jvm是如何识别目标方法的。

刚才我们说到,重载方法的区分在编译阶段已经完成了,那么我们就可以认为在java虚拟机中不存在重载这一概念。因此,重载也可以被称为静态绑定,而重写则被称为动态绑定。

在jvm中,我们有5种方法调用的指令,分别是:

invokestatic:调用静态方法;
invokespecial:调用实例构造方法,私有方法和父类方法,以及使用super关键字调用父类的实例方法或构造器;
invokevirtual:调用虚方法(非私有实例方法);
invokeinterface:调用接口方法,在运行时再确定一个实现此接口的对象;
invokedynamic:在运行时动态解析出调用点限定符所引用的方法之后,调用该方法(jdk1.8lamada表达式);
 
这里,我们简单介绍一下这几种指令,对于invokestatic指令和invokespecial指令而言,java虚拟机能够直接识别目标方法,也就是我们所说的静态绑定
invokevirtual和invokeinterface指令则需要在执行的过程中才能找到目标方法,也就是我们所说的动态绑定
 

总结一下静态绑定和动态绑定的概念就是:

静态绑定:在程序执行之前就已经被绑定、也就是说再编译阶段就已经知道这个方法是属于哪个类的方法。
                1.private修饰的方法,不能被子类调用   2.  被final修饰的方法      3.被static修饰的方法
动态绑定:在运行过程中根据调用者的动态类型来识别目标方法的情况。
动态绑定中,我们会记录方法对应的实际引用的地址,也可以理解为索引值,这里我们把它叫做方法表
方法表使用了数组的数据结构,每个数组元素指向了当前类以及其祖先类中非私有的实例方法。
这个数据结构,便是java虚拟机实现动态绑定的关键所在
 
虚方法
通过这些指令的描述,我们发现虚方法和非虚方法直接决定了静态绑定还是动态绑定,也就决定了是直接用父类的方法还是动态地用子类重写的方法。所以,我们有必要去理解虚方法,并能判断哪些是属于虚方法。
我们先一起看两段代码:
代码1:
class Dota {
private void play() {
System.out.println("我喜欢玩dota,哈哈哈~~~");
}
void startGame() {
play();
}
} class LoL extends Dota {
void play() {
System.out.println("我喜欢玩lol,哈哈哈~~~~");
}
} public class Demo1{
public static void main(String[]args){
new LoL().startGame();
}
}

代码二:

class Dota {
void play() {
System.out.println("我喜欢玩dota,哈哈哈~~~");
} void startGame() {
play();
}
} class LoL extends Dota {
void play() {
System.out.println("我喜欢玩lol,哈哈哈~~~~");
}
} public class Demo2{
public static void main(String[]args){
new LoL().startGame();
}
}

  这里,dota是lol的父类(本人是dotaer,哈哈),这两段代码的唯一不同就是代码1的父类的play方法private修饰的,而代码2中的play()方法不是私有的。接下来,我们看下输出结果:

代码1:

我喜欢玩dota,哈哈哈~~~

代码2:

我喜欢玩lol,哈哈哈~~~~

  第一段代码直接调用了父类的play()方法,而第二段代码调用了子类的play()方法

  大家是不是觉得很奇怪,一个私有的修饰符就能使结果不一样吗?

  结合我们之前所说的进行判别,第一段代码应该是静态绑定,第二段代码则是动态绑定

  那么,代码1就是非虚函数,代码2是虚函数。接下来,我们来看下虚函数的概念:

虚函数:除了静态方法之外,声明为final或者private的实例方法是非虚方法。其它(其他非private方法)实例方法都是虚方法。

        当子类对象调用重写的方法时,调用的是子类的方法,而不是父类中被重写的方法。
        要想调用父类中被重写的方法,则必须使用关键字super。

代码1由于是private修饰,所以为非虚函数,调用了invokespecial指令。

代码2是虚函数,则调用了invokevirtual指令。

再看一个例子

代码3:

public class Demo2 {
static abstract class Game {}
static class Dota extends Game {}
static class Lol extends Game {} public void play(Game game) {
System.out.println("hello,game");
} public void play(Dota dota) {
System.out.println("hello,Dota");
} public void play(Lol lol) {
System.out.println("hello,lol");
} public static void main(String[] args) {
Game dota = new Dota();
Game lol = new Lol();
Demo2 sd = new Demo2();
sd.play(dota);
sd.play(lol);
}
}

输出结果:

hello,game
hello,game

虽然在这里,play方法是虚方法,是动态绑定,但是调用play方法的是Demo2的实例sd,由于Demo2方法没有子类,所以不需要考虑,则直接执行父类的方法。

总结:  

我们介绍了java虚拟机(jvm)是如何执行方法的,我们从我们熟悉的重载和重写切入,了解了静态绑定和动态绑定的概念:

静态绑定:在程序执行之前就已经被绑定、也就是说再编译阶段就已经知道这个方法是属于哪个类的方法。
动态绑定:在运行过程中根据调用者的动态类型来识别目标方法的情况
并且知道了jvm中用方法表来维护非私有实例方法与其索引值的对应关系来实现动态绑定。
接着,我们了解了jvm中调用方法的5个指令,并且通过静态绑定和动态绑定的概念对其进行归类,
静态绑定:
invokestatic:调用静态方法;
invokespecial:调用实例构造方法,私有方法和父类方法;
 
动态绑定:
invokevirtual:调用虚方法;
invokeinterface:调用接口方法,在运行时再确定一个实现此接口的对象;
invokedynamic:在运行时动态解析出调用点限定符所引用的方法之后,调用该方法;
 
归类中通过虚函数的概念,使我们对jvm中方法的调用加以更深的了解。
虚函数:除了静态方法之外,声明为final或者private的实例方法是非虚方法。其它(其他非private方法)实例方法都是虚方法。 
 
 

多态:JVM是如何进行方法调用的的更多相关文章

  1. JVM(十二):方法调用

    JVM(十二):方法调用 在 JVM(七):JVM内存结构 中,我们说到了方法执行在何种内存结构上执行:Java 方法活动在虚拟机栈中的栈帧上,栈帧的具体结构在内存结构中已经详细讲解过了,下面就让我们 ...

  2. 04 JVM是如何执行方法调用的(上)

    重载和重写 重载:同一个类中定义名字相同的方法,但是参数类型或者参数个数必须不同. 重载的方法在编译过程中就可完成识别.具体到每一个方法的调用,Java 编译器会根据所传入参数的生命类型来选取重载方法 ...

  3. 图解JVM执行引擎之方法调用

    一.方法调用 方法调用不同于方法执行,方法调用阶段的唯一任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程.Class文件的编译过程中不包括传统编译器中的连接步骤,一 ...

  4. 04 JVM是如何执行方法调用的(下)

    虚方法调用 Java 里所有非私有实例方法调用都会被编译成 invokevirtual 指令,而接口方法调用会被编译成 invokeinterface 指令.这两种指令,均属于 Java 虚拟机中的虚 ...

  5. 深入解析多态和方法调用在JVM中的实现

    深入解析多态和方法调用在JVM中的实现 1. 什么是多态 多态(polymorphism)是面向对象编程的三大特性之一,它建立在继承的基础之上.在<Java核心技术卷>中这样定义: 一个对 ...

  6. JVM方法调用

    当我们站在JVM实现的角度去看方法调用的时候,我们自然会想到一种分类: 1.编译代码的时候就知道是哪个方法,永远不会产生歧义,例如静态方法,private方法,构造方法,super方法. 2.运行时才 ...

  7. JVM方法调用过程

    JVM方法调用过程 重载和重写 同一个类中,如果出现多个名称相同,并且参数类型相同的方法,将无法通过编译.因此,想要在同一个类中定义名字相同的方法,那么它们的参数类型必须不同.这种方法上的联系就是重载 ...

  8. java方法调用之动态调用多态(重写override)的实现原理——方法表(三)

    上两篇篇博文讨论了java的重载(overload)与重写(override).静态分派与动态分派.这篇博文讨论下动态分派的实现方法,即多态override的实现原理. java方法调用之重载.重写的 ...

  9. Spring杂谈 | 从桥接方法到JVM方法调用

    前言 之所以写这么一篇文章是因为在Spring中,经常会出现下面这种代码 // 判断是否是桥接方法,如果是的话就返回这个方法 BridgeMethodResolver.findBridgedMetho ...

随机推荐

  1. 模板引擎StringTemplate和模板StringTemplateGroup的应用

    博主很懒什么都没有留下,只留下了一个转载链接!!! http://www.cnblogs.com/Jerry-Chou/archive/2012/12/12/2814693.html

  2. 【扩展GCD】荒岛野人

    题目 [题目描述] 克里特岛以野人群居而著称.岛上有排列成环行的M个山洞.这些山洞顺时针编号为1,2,-,M.岛上住着N个野人,一开始依次住在山洞C1,C2,-,CN中,以后每年,第i个野人会沿顺时针 ...

  3. 剑指offer-正则表达式匹配-字符串-python****

    # -*- coding:utf-8 -*- ''' 题目:请实现一个函数用来匹配包括'.'和'*'的正则表达式. 模式中的字符'.'表示任意一个字符(不包括空字符!),而'*'表示它前面的字符可以出 ...

  4. 关于Android studio调用高德地图的简单流程和要点

    一,账号与Key的申请 注册成为高德开发者需要分三步: 第一步,注册高德开发者:第二步,去控制台创建应用:第三步,获取Key. 前2步都比较简单,这里说下第三步. 获取Key 1.进入控制台,创建一个 ...

  5. eclipse 保存web.xml 和 loading description from 问题的解决

    Eclipse 版本为 2019-06 (4.12.0) 发现开启的时候一直有loading description from ***  ,这个loading description 是web项目加载 ...

  6. N4_75条语法

    1. コ/ソ/ア/ド体系 -(こ.そ.あ.ど)れ/-(こ.そ.あ.ど)の A:-(こ.そ.あ.ど)れ 接续: 指示代词和场所代词,分近称.中称.远称.疑问称. 意思: 这个,那个,那个,哪个 例:これ ...

  7. ubuntu - 如何以root身份使用图形界面管理文件?

    nautilus 是gnome的文件管理器,但是如果不是root账号下,权限受限,我们可以通过以下方式以root权限使用! 一,快捷键“ctrl+alt+t”,调出shell. 二,在shell中输入 ...

  8. 框架之一:mybatis

    同时加载驱动包 mysql+mybatis 创建bean对应的mapper接口bookMapper package com.imu.mzw.mapper; import java.util.List; ...

  9. pat(B)_1001

    1001 害死人不偿命的(3n+1)猜想 (15 分) 卡拉兹(Callatz)猜想: 对任何一个正整数 n,如果它是偶数,那么把它砍掉一半:如果它是奇数,那么把 (3n+1) 砍掉一半.这样一直反复 ...

  10. SpringBoot布道系列 | 目录汇总 | 2019持续更新ing

    SpringBoot 基础教程 | 三大推荐理由 1.文章内容均为原创,结合官方文档和实战经验编写. 2.文章结构经过细致整理,对新人学习更加友好. 3.精选常用技术,不求全面,但求精华!! Spri ...