JVM角度理解重写和重载

两个概念

方法调用:确定被调用方法的版本

  • 解析:类加载期间确定,由符号引用转为直接引用,
    invokestatic(调用静态方法)
    invokespecial(调用实例构造器init方法、私有方法、父类中的方法)
  • 分派:运行期间确定,确定目标方法的直接引用
    invokevirtual(调用虚方法)
    invokeinterface(调用接口方法,在运行时确定一个实现该接口的对象)
    invokedynamic(运行时动态解析出调用点限定符所引用的方法,然后再执行该方法)

方法执行:由执行引擎执行此方法

解析

解析的定义

在类加载的解析阶段,会将其中的一部分符号引用转为直接饮用,这种解析的前提是:方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的。也就是说,调用目标在程序代码写好、编译器进行编译的那一刻就已经确定下来。这就是方法调用中的解析

在Java当中符合“编译期可知,运行期不可变”的方法,主要有静态方法和私有方法两大类,因为静态方法与类型直接关联,私有方法在外部不可见。这两种方法的共同特点决定了它们适合在类加载阶段进行解析。

被invokestatic和invokespecial指令调用的方法都可以在解析阶段中唯一确定调用版本。具体包含静态方法、私有方法、实例构造器、父类方法这4种,以及被final修饰的方法,这五种方法调用会在类加载的时候把符号引用解析为该方法的直接引用。称为非虚方法。

final方法由于历史设计的原因,也是使用invokevirtual指令来调用的,但是因为它无法被覆盖、也没有其他版本的可能,所以也可以放在解析的过程中进行调用

分派

重载:静态分派
重写:动态分派

静态类型和实际类型

Human human = new Man()

其中Human称为变量的“静态类型”,或者叫“外观类型”
其中Man则被称为变量的“实际类型”或者叫“运行时类型”
其中二者的区别为:静态类型在编译期是可知的、实际类型的变化在运行期才可以确定。

静态分派:重载

public class StaticDispatch {
	// 重载方法,按照静态类型判定具体选择哪个
    public void sayHello(Human guy) {
        System.out.println("hello guy");
    }

    public void sayHello(Man man) {
        System.out.println("hello man");
    }

    public void sayHello(Woman woman) {
        System.out.println("hello woman");
    }

    public static void main(String[] args) {
        StaticDispatch staticDispatch = new StaticDispatch();
        // 静态类型Human 实际类型Man
		Human man = new Man();
        Human woman = new Woman();
		// 根据静态类型Human确定了调用方法的版本
        staticDispatch.sayHello(man);
        staticDispatch.sayHello(woman);
    }

}

abstract class Human {}
class Man extends Human{}
class Woman extends Human{}

虚拟机在重载时是通过参数的静态类型来作为判定依据的,而不是实际类型。
所以Javac编译器就根据参数的静态类型决定了会使用哪个重载版本,因此选择了sayHello(Human human)作为调用的目标

动态分派:重写

public class DynamicDispatch {
    public static void main(String[] args) {
        Human human = new Woman();
        Human woman = new Man();
        human.sayHello();
        woman.sayHello();
    }
}

abstract class Human {
    protected abstract void sayHello();
}

class Woman extends Human {

    @Override
    protected void sayHello() {
        System.out.println("i am woman");
    }
}

class Man extends Human {

    @Override
    protected void sayHello() {
        System.out.println("i am man");
    }
}

其方法的调用指令都是通过字节码指令invokevirtual来实现
invokevirtual指令的运行时解析过程大致分为以下几步

  1. 找到操作数栈顶的第一个元素所指向的对象的实际类型 C
  2. 如果在类型 C 中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限验证,如果通过则返回这个方法的直接引用,不通过则返回java.lang.IllegalAccessError异常。
  3. 如果在类型 C 中没有找到与常量中的描述符和简单名称都相符的方法,则按照继承关系从下往上对 C 的各个父类进行第二步的搜索和验证过程
  4. 如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常

正是因为invokevirtual在两次调用中invokevirtual指令并不是把常量池中方法的符号引用解析到直接引用上就结束了,而是会接着得到确定接受者的实际类型,然后根据实际类型来选择方法版本,这个过程就是Java语言中方法重写的本质。这 种 运行时根据实际类型确定方法执行版本的分派过程就称之为动态分派。而多态性的根源就在于虚方法调用指令invokevirtual的执行逻辑,所以对方法有效,而对字段无效。

静态多分派与动态单分派

静态多分派:选择目标方法的依据有两点:静态类型,方法参数
动态单分派:选择目标方法的依据只有一点:实际类型

虚拟机动态分派的实现

动态分派是执行非常频繁的动作,而且动态分派的方法版本选择过程需要运行时在接收者类型的方法元数据中搜索合适的目标方法,因此Java虚拟机实现基于执行性能考虑的,会为每个类型在方法区当中建立一个虚方法表以及接口方法表来代替元数据查找以提高性能。

虚方法中存放着各个方法的实际入口地址,即当前类的方法可能使用的是其他类的类型数据。如果某个方法在子类中没有被重写,则子类和父类的虚方法表的方法地址入口相同,均指向父类方法的地址入口。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
(1) JDK(Java Development Kit)是Java开发工具包,包含了Java编译器、Java运行环境、Java API等工具和库,是Java程序开发的必备工具。JRE(Java Runtime Environment)是Java运行环境,包含了Java虚拟机(JVM)和Java运行所需要的库文件等,用于运行Java程序。 (2) 方法的重写是指子类定义了与父类名字、参数列表和返回类型都相同的方法,这个过程也称为方法的覆盖。在子类中重写父类的方法,可以改变父类方法的实现,达到自己的需求。 (3) JAVA多态的三种形式包括:方法重载、方法重写和接口实现。 (4) 创建一个名为arrs的含有10个元素的byte数组的代码如下: ``` byte[] arrs = new byte[10]; ``` (5) 线程的五种状态包括:新建状态、就绪状态、运行状态、阻塞状态和死亡状态。 (6) 如果一个类被声明为final,表示该类不能被继承。final类中的方法可以被子类继承,但不能被重写。 (7) 方法的重载是指在一个类中定义了多个同名的方法,但参数列表不同,返回类型可以相同,也可以不同。编译器会根据调用方法时传入的参数类型和数量来确定调用哪个方法。 (8) 面向对象的三大特征包括:封装、继承和多态。 (9) 创建一个名为arrs的含有10个元素的char数组的代码如下: ``` char[] arrs = new char[10]; ``` (10) 阻塞状态是指线程因为等待某个操作完成而暂时停止运行,直到等待的操作完成后才会重新进入就绪状态。 (11) break和continue都是Java中的关键字。break用于跳出循环,执行循环后面的语句;continue用于跳过本次循环,执行下一次循环。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值