`
Mysun
  • 浏览: 270536 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

java方法调用过程解析和执行--编译器的处理

阅读更多
本文尝试对java在编译器和运行期如何处理程序代码中的方法调用表达式进行描述,本文的大部分内容来自于java语言规范3.0.
由于java动态语言的特性,因此它在编译期和运行期都需要对程序代码中的方法调用表达式进行处理。其中对方法调用表达进行处理的大部分工作是在编译期完成的,而运行期的大部分工作则是对编译完成的方法调用表达式进行有效性检查。
编译期完成的处理
在编译期,java需要对方法调用表达式进行检查和分析,如果表达式能够被正确地解释,那么java的编译器需要将有关方法调用的元数据记录到字节码文件中,为jvm能够在运行期正确地执行方法调用提供足够多的信息。java编译器要完成的主要工作可以用表1中的伪代码来表示。
根据表达式确定要在那些类和接口中搜索方法;
if 找到了一个或者多个类和接口
    尝试从这些类和接口中找到所有潜在可用方法定义;
    if 找到了一个或者多个可用的方法定义
        从这些方法定义中找出与方法调用表达式最为匹配的一个方法定义;
        if 找到了一个最为匹配的方法
            对方法调用的语法进行检查;
            if 方法调用具有正确的语法
                将运行期需要的相关信息写入字节码中;
            else
                编译器产生错误;
        else
            编译器产生错误;
    else
        编译器产生错误;
else
    编译器产生错误;

在本文后面的叙述中,大家会发现,编译器的大部分精力需要花在为一个方法调用表达式确定一个最为匹配的方法定义,而这一工作之所以会被复杂化则是由于java对泛型的引入。另外,对于java来说,在确定需要调用哪个方法时,方法的返回值的确非常的不重要,通过后面的叙述大家也可以看到,java将方法的返回值排除在方法签名之外确实是有道理的。
确定要在哪些类与接口中搜索方法定义
我们知道,在java这样的面向对象语言中,方法是定义在类或者接口中的。所以,当java编译器面对一个方法调用表达式时,为了找到被调用的这个方法,首先要做的就是找到可能会定义这个方法的类或者接口。编译器会根据方法调用表达式的形式来确定要搜索的类和接口,表2列出了相关信息。表中列出的方法调用形式涵盖了我们可以在java中使用的所有方法调用形式。在这一步中找到的类和接口将作为后面进步进行解析的输入,最终被确定的方法将在这一步中找到的类或者接口中产生。另外值得一提的是,在搜索备选的类或者接口时,编译器是根据方法名来查找的,方法参数的个数及其类型在这一步是用不到的。
编号表达式形式要搜索的类和/或接口
1methodName(args) 对于这种形式的方法调用,如果是正确的,那么对于这条语句所在的作用域来说,必然会有一个可见的方法定义。这个方法定义可能是在类中,也可能是在内部类中,那么要搜索的类或者接口就是定义了这个方法的最内层的类或者接口。简单来说,我们要找的类或者接口就是包含这条语句的类及其外部类。
2TypeName[1].methodName(args) 对于这种形式的方法调用,要搜索的类就是TypeName所定义的那个类,如果TypeName定义的是一个接口,编译器会给出编译错误。因为这种类型的调用指定针对静态方法,而接口是不能有任何形式的方法定义的,它只能包含方法的声明。
3Primary.<NonWildTypeArguments>methodName(args) Primary是java规范中定义的初级表达式,用来构成其他更加复杂的表达式(参见java规范15.8节)。如果Primary表达式的结果是一个接口或者类,那么这个接口或者类就是我们要搜索的。如果Primary是一个类型参数T,那么要搜索的接口或者类就是T的上届。
4super.<NonWildTypeArguments>methodName(args) 在这种情况下,要搜索的类是包含了这条方法调用语句的类的超类。假设包含这条方法调用语句的类是T,如果T是Object(Object是没有超类得)或者T是一个接口(接口是不能定义方法的),那么编译器会给出编译错误。
5ClassName[1].super.<NonWildTypeArguments>methodName(args) 这种类型的调用语句,要搜索的类式ClassName代表的那个类的超类。如果包含这条语句的类不是ClassName代表的那个类的子类,编译器会给出错误。如果ClassName代表的那个类式Object,编译器也会给出错误。另外,如果包含这条语句的类是Object或者是一个接口,那么编译器同样会给出错误。
6FieldName.methodName(args) 这里FieldName表示类的属性,要搜索的类就是这个属性名所代表的那个类。如果FieldName代表的是一个类型参数,那么要搜索的类是这个类型参数的上届。
7TypeName.<NonWildTypeArguments>methodName(args)同2

确定方法签名
在这一步中,编译器会根据被调用的方法签名(方法名加上参数),在上一步得到的接口或者类中搜索可用的所有方法(方法定义符合调用语句中给出的参数个数及其类型)。如果可能用的方法有多个,那么编译器必须决定哪个方法定义是最符合调用语句期望的(chose the most specific method)。
考虑到兼容性的原因,搜索可用方法的过程被却分为三个阶段:
  • 在第一个阶段中,编译器在不允许自动装箱和解箱以及不考虑变长参数列表的情况下,执行方法搜索;
  • 在第二个阶段中,编译器在不考虑变长参数列表,但是允许自动装箱和解箱的情况下,执行方法搜索;
  • 在第三个阶段中,编译器在允许自动装箱和解箱以及变长参数列表的情况下,执行方法搜索;

编译器从第一个阶段开始执行,当第一个阶段完成之后,如果发现没有找到任何可用的方法定义,那么就会执行第二个阶段,以此类推。在任何一个阶段中,如果找到了一个以上的可用方法定义,就不会继续执行后面的阶段。
在每个阶段中,java都会试图找出符合以下要求的所有方法定义:
  • 定义的方法名与调用语句中的方法名一致(大小写敏感的);
  • 方法定义对于方法调用语句来说是可以见的;
  • 方法定义中的参数个数必须小于等于方法调用语句中提供的参数个数(变长参数被视为一个方法参数);
  • 如果方法定义的参数列表中包含了变长参数,假设方法定义有n个参数(变长参数被视为一个方法参数),那么方法调用语句提供的参数列表长度必须大于等于n-1;
  • 如果方法定义不包含变长参数,假设方法定义有n个参数,那么方法调用语句提供的参数列表的长度也必须是n;
  • 如果方法调用语句包含了显示的类型参数,并且方法声明也是泛型的,那么方法调用语句中的真实类型参数个数必须与方法声明中的形式化类型参数的格式保持一致;
  • 方法调用语句中参数列表中的参数类型必须依次能够与方法声明中参数列表的参数类型匹配;

从上面的定义中不难发现,java是允许泛型方法调用匹配到非泛型方法声明上的,这是兼容性和可替换性的要求。兼容性比较好理解,如果我们用使用了泛型的代码,调用一个用较早版本写出来的类库,这种调用也是应该成功的。可替换性是针对子类化和接口实现而言的,因为子类或者实现类可以将超类或者接口中的泛型方法覆写(override)成非泛型的方法,为了保证超类能用的地方子类也必然能用的这条规则,所以允许泛型方法调用匹配非泛型方法声明。
在上述几点中,最后一点,也就是关于方法参数类型匹配的过程相较其他几点要复杂一些。因为在Java中允许子类化、允许使用表达式的值作为方法的参数、允许变长参数,另外还有泛型参与其中,导致了处理上的复杂。在java规范中对此有比较详细的说明,因为多是数学上的一些推导,这里就不累述了。相关参见java规范15.12.2.2-15.12.2.4节内容。
重载方法决断
当编译器发现有多个方法都可以满足方法调用表达式时,这个时候就要选择一个方法声明作为运行时正真调用的方法。当然,Java规范对这个过程也有详细的数学定义,这里也就不说了。相关内容可以参见java规范15.12.2.5节
这里需要提一下对抽象方法(abstract method)的特殊处理,如果编译器发现了的多个最合适的方法,但是其中只有一个是非抽象方法,那么这个非抽象方法就是运行时要调用的方法。如果所有的方法都是抽象的,那么编译器会从具有最具体(most specific)返回类型的子集中随机选择一个来作为运行时调用的方法。所以,对于抽象方法的定义还是要注意一些。
关于类型推导
从1.5开始,java引入了泛型,这给编译器处理方法调用增加了不少的复杂度,其中很大一部分来自方泛型方法调用时候的类型推导。关于这个部分java规范上内容颇多,而且都是一些数学推导和定义,对平日代码编写也没有特别大的用处,这里就详细讲述了,有兴趣可以看一下java规范上的相关描述
扫尾处理
编译器在为一个方法调用语句选择了一个最合适的方法声明之后,会将相关信息写入class文件,这个方法声明便被称为编译时方法声明(compile-time declaration)。之后编译器还会进行进一步的校验。比如一个实例方法被放到一个静态上下文中调用,或者super.methodName这样的调用最后被发现找到的是一个抽象方法,又或者返回类型为void的方法被放到了负值语句或者被当作其他方法调用的参数,这个时候编译器都会给出编译期的异常。
分享到:
评论
发表评论

文章已被作者锁定,不允许评论。

相关推荐

    compiler:C--使用Antlr4的编译器

    该编译器利用Antlr4自动生成前端解析器和扫描器。 编译器的中间和后端是用C ++编写的。 目标程序集是Jasmin,Jasmin是一种JVM汇编语言,可以将其汇编为Java * .class文件。 从那里,可以调用JVM来执行程序。 目录...

    java-servlet-api.doc

    你也许对下面的这些Internet规范感兴趣,这些规范将直接影响到ServletAPI的发展和执行。你可以从http://info.internet.isi.edu/7c/in-notes/rfc/.cache找到下面提到的所有这些RFC规范。 RFC1738统一资源定位器(URL) ...

    易语言程序免安装版下载

     使用说明如下:函数声明和调用方法与DLL命令一致;“库文件名”以.lib或.obj为后缀的将被视为静态库,可使用绝对路径或相对路径(相对当前源代码所在目录),如依赖多个静态库请分别列出并以逗号分隔;“在库中的...

    JWebAssembly:Java字节码到WebAssembly编译器

    Java字节码解析器 测试框架 编译器的公共API参见 二进制格式文件编写器和文本格式文件编写器 支持本机方法 内存管理-当前在JavaScript端具有polyfill 调用静态方法调用 调用实例方法调用 调用接口方法调用 ...

    Java 1.6 API 中文 New

    javax.xml.crypto.dsig.keyinfo 用来解析和处理 KeyInfo 元素和结构的类。 javax.xml.crypto.dsig.spec XML 数字签名的参数类。 javax.xml.datatype XML/Java 类型映射关系。 javax.xml.namespace XML 名称空间处理...

    Tiger-Compiler:这是一个Java 编译器,用于小老虎语言。 Tiger 支持基本运算、自定义数据类型和浮点运算。 目标平台为MIPS,目前未实现函数调用。 存在扫描、解析、代码优化和生成。 编译器前端采用ANTLR构建,采用抽象语法树、四元组指令表等多种中间代码形式

    老虎编译器这是一个Java 编译器,用于小老虎语言。 Tiger 支持基本运算、自定义数据类型和浮点运算。 目标平台为MIPS,目前未实现函数调用。 存在扫描、解析、代码优化和生成。 编译器前端采用ANTLR构建,采用抽象...

    Java 虚拟机面试题全面解析(干货)

    虚拟机是如何执行方法里面的字节码指令的? 解释执行 基于栈的指令集和基于寄存器的指令集 什么是基于栈的指令集? 什么是基于寄存器的指令集? 基于栈的指令集的优缺点? Javac编译过程分为哪些步骤? 什么是即时编译器?...

    java相关的2024面试题集锦

    生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或者设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)的时候,以及调用一个类静态方法的时候 ...

    java 面试题 总结

    java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。 6、说出Servlet的生命周期,并说出Servlet和CGI的区别。 Servlet被服务器实例化后,容器运行其init方法,...

    Java核心技术II(第8版)

    11.1.4 调用脚本的函数和方法 11.1.5 编译脚本 11.1.6 一个示例:用脚本处理GUI事件 11.2 编译器API 11.2.1 编译便捷之法 11.2.2 使用编译工具 11.2.3 一个示例:动态Java代码生成 11.3 使用注解 11.3.1 一个示例:...

    Java虚拟机

    这本书的内容是帮你全面了解java虚拟机,本书第1版两年内印刷近10次,98%以上的评论全部为5星级的好评,是整个Java图书领域公认的经典著作和超级畅销书,繁体版在台湾也十分受欢迎。第2版在第1版的基础上做了很大的...

    java安卓仿微信聊天软件源码-myStars:我的明星名单

    java安卓仿微信聊天软件源码真棒明星 我的 GitHub 星星的精选列表! 生成者 内容 集会 - 收集各种不同编程语言的各种平台的恶意软件源代码。 - 每种计算机语言中的 Hello world。 感谢为此做出贡献的每个人,请务必...

    深入理解_Java_虚拟机 JVM_高级特性与最佳实践

    / 199 8.2.2 操作数栈 / 204 8.2.3 动态连接 / 206 8.2.4 方法返回地址 / 206 8.2.5 附加信息 / 207 8.3 方法调用 / 207 8.3.1 解析 / 207 8.3.2 分派 / 209 8.4 基于栈的字节码解释执行引擎 / 221 8.4.1 ...

    java8看不到源码-java8-plugin-persitent-local-vars:javac(openJDK8)的插件,增加了对Jav

    这样做的原因是编译器使用的偏移字段值是在初始解析之后计算的,因此,在注入新字段之前。 为了解决这个问题,必须在注入新字段时手动更新偏移值。 这是什么 与 C99 static关键字类似,它用于指示一个局部变量,该...

    超级有影响力霸气的Java面试题大全文档

    java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。 9、说出Servlet的生命周期,并说出Servlet和CGI的区别。  Servlet被服务器实例化后,容器运行其init方法...

    AIDL使用规范及调用过程解析(Android Q)

    AIDL的全称是Android Interface definition language,一看就明白,它是一种android内部进程通信接口的描述语言,通过它我们可以定义进程间的通信接口,用处当然就是用来进程间的通信和方法调用了。其实AIDL是Binder...

    新版Android开发教程.rar

    的 Android SDK 提供了在 Android 平台上使用 JaVa 语言进行 Android 应用开发必须的工具和 API 接口。 特性 • 应用程序框架 支持组件的重用与替换 • Dalvik Dalvik Dalvik Dalvik 虚拟机 专为移动设备优化 • ...

    java断点续传源码解析-particle_eclipse_debug:使用Eclipse和OpenOCD调试粒子光子和电子代码

    java断点续传源码解析使用 Eclipse 进行粒子调试 使用 JTAG/SWD 和 Eclipse 调试 Particle Photon/P1/Electron 代码 此常见问题解答介绍了如何使用 Eclipse(适用于 Windows、Mac 和 Linux 的免费 IDE)以及 OpenOCD...

    [Java参考文档].JDK_API 1.6

    javax.xml.crypto.dsig.keyinfo 用来解析和处理 KeyInfo 元素和结构的类。 javax.xml.crypto.dsig.spec XML 数字签名的参数类。 javax.xml.datatype XML/Java 类型映射关系。 javax.xml.namespace XML 名称空间处理...

    将 Flex 集成到 Java EE 应用程序的最佳实践(完整源代码)

    MessageBrokerServlet 是真正处理 Flex 远程调用请求的 Servlet,我们需要将其映射到指定的 URL: 清单 5. 定义 Flex servlet &lt;servlet&gt; &lt;servlet-name&gt;messageBroker&lt;/servlet-name&gt; &lt;servlet-class&gt;...

Global site tag (gtag.js) - Google Analytics