1 怎么理解Java的方法分派(分派调用)?

答:Java的方法分派是三大特性之一的多态性的体现,分为静态分派与动态分派。

静态分派发生在在编译期阶段,依据调用者的静态类型(也就是:声明类型和方法参数类型)来确定方法执行版本;典型应用是方法重载,确定静态分派的动作实际上不是由虚拟机来执行的动态分派发生在运行时阶段,依据调用者的实际类型来确定方法执行版本;典型应用是方法重写,确定动态分派的动作是由虚拟机来执行的。横向对比其他语言,Kotlin中与Java是一致的效果;而Groovy是动态语言,一切依据调用者的实际类型来确定方法执行版本

1.1 这道题想考察什么?

答:(1)考察要点
●多态、虚方法表的认识(初级)
●对编译和运行时的理解和认识(中级)
●对Java语言规范和运行机制的深入认识(高级)
●横向对比各类语言的能力(高级):Groovy , Gradle DSL 5.0以前唯一正式语言;C++ , Native程序开发必备

(2)题目剖析
●就是确定调用谁的、哪个方法;针对方法重载的情况进行分析;针对方法覆写的情况进行分析。

2 怎么理解Java的解析调用?

2.1 解析调用 和 分派调用 的关系?

答:方法调用具有 解析调用 和 分派调用 方式在方法调用中,解析调用与分派调用这两者之间的关系并不是二选一的排他关系,它们是在不同层次上去筛选、确定目标方法的过程。例如,静态方法会在类加载阶段就进行解析,而静态方法显然也是可以拥有重载版本的,选择重载版本的过程也是通过静态分派完成的

2.2 什么是解析调用?

答:方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程。其中在类装载的解析阶段就会把涉及的符号引用全部转变为可确定的直接引用,不会延迟到运行期再去完成,换句话说,编译器进行编译时就必须确定下来,这类方法的调用称为解析调用

2.3 非虚方法 和 虚方法 的区别?

答:只要能被 invokestatic 和 invokespecial 指令调用的方法,都可以在解析阶段中确定唯一的调用版本,符合这个条件的有静态方法、私有方法、实例构造器、父类方法4类,它们在类加载的时候就会把符号引用解析为该方法的直接引用。这些方法可以称为非虚方法,与之相反,其他方法称为虚方法(除去final方法,后文会提到)。

Java中的非虚方法除了使用 invokestatic、invokespecial 调用的方法之外还有一种,就是被 final修饰的方法。虽然final方法是使用 invokevirtual 指令来调用的,但是由于它无法被覆盖,没有其他版本,所以也无须对方法接收者进行多态选择,又或者说多态选择的结果肯定是唯一的。所以,静态方法、私有方法、实例构造器、父类方法以及final方法的调用都是 解析调用

2.4 Java虚拟机里面提供了哪5条方法调用字节码指令?

答:指令 | 方法
(1)invokestatic | 调用静态方法
(2)invokespecial | 调用实例构造器<init>方法、私有方法和父类方法
(3)invokevirtual | 调用所有的虚方法
(4)invokeinterface | 调用接口方法,会在运行时再确定一个实现此接口的对象;
(5)invokedynamic | 先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法,在此之前的4条调用指令,分派逻辑是固化在Java虚拟机内部的,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。

2.5 学习链接

深入理解Java虚拟机笔记—方法调用

3 怎么理解Java的方法分派(分派调用)?

3.1 什么是Java的方法分派?

答:Java中有三大特性:封装、继承和多态。分派是多态性的体现,分为静态分派与动态分派

3.2 什么是静态类型与实际类型?

答:(1)静态类型:“SuperClass”称为变量的静态类型(Static Type),或者叫做的外观类型(Apparent Type),包括:声明类型和方法参数类型。静态类型的变化仅仅在使用时发生,变量本身的静态类型不会被改变,并且最终的静态类型是在编译期可知的

val superClass: SuperClass = SubClass()

(2)实际类型:后面的“SubClass”则称为变量的实际类型(Actual Type)。实际类型变化的结果在运行期才可确定,编译器在编译程序的时候并不知道一个对象的实际类型是什么。例如下面的代码:

java 类的派生类 java怎么定义派_Java

3.3 什么是静态分派与动态分派?

答:(1)静态分派发生在编译期阶段,依据调用者的静态类型(声明类型和方法参数类型)来确定方法执行版本。典型应用是方法重载,确定静态分派的动作实际上不是由虚拟机来执行的。特殊的,编译器虽然能确定出方法的重载版本,但在很多情况下这个重载版本并不是唯一的,往往只能确定一个“更加合适的”版本。产生这种模糊结论的主要原因是字面量不需要定义,所以字面量没有显式的静态类型,它的静态类型只能通过语言上的规则去理解和推断。

(2)动态分派发生在运行时阶段,依据调用者的实际类型来确定方法执行版本。典型应用是方法重写,确定动态分派的动作是由虚拟机来执行的

java 类的派生类 java怎么定义派_Java_02

3.4 针对(静态分派)方法重载的情况进行分析?

答:

java 类的派生类 java怎么定义派_静态类_03


java 类的派生类 java怎么定义派_java 类的派生类_04

3.4.1 通过字节码分析原理

java 类的派生类 java怎么定义派_Java_05

// Polymorphisn$SuperClass
L1
LINENUMBER 20 L1
ALOAD 0
ALOAD 1
INVOKEVIRTUAL com/read/kotlinlib/polymorphisn/Polymorphisn.printHello
 (Lcom/read/kotlinlib/polymorphisn/Polymorphisn$SuperClass;)V

3.4.2 重载与返回参数无关?

答:

java 类的派生类 java怎么定义派_Java_06

3.5 针对(动态分派)方法覆写的情况进行分析?

答:

java 类的派生类 java怎么定义派_Java_07


java 类的派生类 java怎么定义派_虚方法_08

3.6 在Groovy中是如何运行的?

答:Groovy是动态语言,一切依据调用者的实际类型来确定方法执行版本

java 类的派生类 java怎么定义派_虚方法_09


java 类的派生类 java怎么定义派_Java_10