从零开始开发JVM语言(十二)重载方法的选择
2016-06-30 00:00
295 查看
摘要: 编译器 语义分析
目录戳这里
方法重载在面向对象里很基础,不管是学校还是自学时都会教到这个概念。实际上相互重载的方法除了名字相同之外,并没有任何关系,因为它们拥有完全不同的方法签名。
JVM的
先创建一个
方法签名由两部分构成,
方法所在类型,例如
方法的
而后者又由两部分组成
方法参数,用小括号包围,例如
方法返回值,也是一个类型,例如
所以说,两个重载方法之间,对于JVM来说一点关系都没有,对于使用者(我们)来说,相关联的仅仅是名称相同而已。编译中要做的事情是根据方法名称和给定的参数类型‘定位’到要调用的方法。
从直观上就可以看出,想找到要调用的方法有这几个因素需要满足:
方法能够被访问到
方法名称一致
方法参数长度一致
每个参数(parameter)的类型都要能够由变量(argument)转换而来,例如
其中
(最关键的一点)方法应当是最佳的。
什么是最佳的方法呢?例如
我们都知道,如果给出
最后结果是
最后结果是
这个例子是基本类型和引用类型间的,它们区别比较大,再看一个同为引用类型的例子:
上述定义的
当然,如果把List向上转换为
在编译时(动态类型语言在运行时也)要保证调用的方法“最佳”。我定义的“最佳”是: 在转换到目标类型所需的“距离”最短。
这里的“距离”在之前 step 3 检查合法性时就提到过了。向上转型一次,距离就+1。而
最终可以得到一些这样的“元组”,表示每个参数的转换次数
例如:方法为
参数和对应元组:
这样一来,前者每个数字都不大于后者,所以应当调用前者。
但是如果有这种情况方法为
那就无法确定了,因为
会报错:两个方法都匹配。所以有的地方才会在方法调用参数上加一个类型。
那么如果出现需要“隐式转换”的情况呢?只要加上一个比较大的常数即可。实际没什么太大区别。
放个代码:编译期的方法匹配戳这里。编译期没有完全的使用本篇的距离,而是直接判断“好坏”.
运行时的方法寻找戳这里,这和本篇说的方式一模一样,因为有隐式类型转换的存在,所以不得不加个距离。
最后,希望看官能够关注我的编译器哦~Latte
目录戳这里
方法重载在面向对象里很基础,不管是学校还是自学时都会教到这个概念。实际上相互重载的方法除了名字相同之外,并没有任何关系,因为它们拥有完全不同的方法签名。
JVM的
invokeXXX字节码(除了
invokedynamic,其用法前文已写),都需要一个方法签名来指定调用哪个方法。例如
new java/lang/Object dup invokespecial java/lang/Object:<init>()V
先创建一个
java.lang.Object对象,然后再栈中复制这个“未初始化的对象”,接着调用构造函数进行初始化。
方法签名由两部分构成,
方法所在类型,例如
java/lang/Object
方法的
descriptor
而后者又由两部分组成
方法参数,用小括号包围,例如
(Ljava/lang/String;I)就代表了
(java.lang.String, int)
方法返回值,也是一个类型,例如
Ljava/util/regex/Pattern;
所以说,两个重载方法之间,对于JVM来说一点关系都没有,对于使用者(我们)来说,相关联的仅仅是名称相同而已。编译中要做的事情是根据方法名称和给定的参数类型‘定位’到要调用的方法。
从直观上就可以看出,想找到要调用的方法有这几个因素需要满足:
方法能够被访问到
方法名称一致
方法参数长度一致
每个参数(parameter)的类型都要能够由变量(argument)转换而来,例如
String.valueOf('abc')
其中
'abc'是字符串,就不能匹配到
String.valueOf(int)这个方法
(最关键的一点)方法应当是最佳的。
什么是最佳的方法呢?例如
java.util.List有这两个方法
remove(int) remove(Object)
我们都知道,如果给出
int型,就会调用前者,如果给出引用类型,例如
java.lang.Integer,就会调用后者。比方说:
list = [1,2,3] list.remove(1)
最后结果是
[1,3]。如果是
list.remove(Integer(1))
最后结果是
[2,3]。
这个例子是基本类型和引用类型间的,它们区别比较大,再看一个同为引用类型的例子:
class List concat(o : Object) : List concat(list : List) : List
上述定义的
concat方法,一个接收
Object类型,一个接收
List类型。在调用时,如果给出
list.concat('abc'),就需要调用
Object的重载,如果给出
list.concat([1,2,3])就需要调用
List的重载。
当然,如果把List向上转换为
Object再来调用,比如
list.concat([1,2,3] as Object)会调用
Object的重载,因为编译器只管给定的类型,不会不管(有的时候也不可能知道)确切的类型。
在编译时(动态类型语言在运行时也)要保证调用的方法“最佳”。我定义的“最佳”是: 在转换到目标类型所需的“距离”最短。
这里的“距离”在之前 step 3 检查合法性时就提到过了。向上转型一次,距离就+1。而
int到
Integer这样的转换是“隐式类型转换”,需要加上一个常数,比如
10000,一个比较大的数字,保证正常的向上转型永远不会大于隐式转换,也即隐式转换优先级更低。
最终可以得到一些这样的“元组”,表示每个参数的转换次数
例如:方法为
(String, Object)和
(CharSequence, Object)
参数和对应元组:
(String, List) -- (0, 1)/(1, 1)
这样一来,前者每个数字都不大于后者,所以应当调用前者。
但是如果有这种情况方法为
(String, Object)和
(CharSequence, List)
(String, List) -- (0, 1)/(1, 0)
那就无法确定了,因为
前._0<
后._0但是
前._1>
后._1。Java也是如此,如果有兴趣可以尝试一下。
void a(CharSequence c, List list){} void a(String c, Object list){} a("a",new ArrayList());
会报错:两个方法都匹配。所以有的地方才会在方法调用参数上加一个类型。
那么如果出现需要“隐式转换”的情况呢?只要加上一个比较大的常数即可。实际没什么太大区别。
放个代码:编译期的方法匹配戳这里。编译期没有完全的使用本篇的距离,而是直接判断“好坏”.
运行时的方法寻找戳这里,这和本篇说的方式一模一样,因为有隐式类型转换的存在,所以不得不加个距离。
最后,希望看官能够关注我的编译器哦~Latte
相关文章推荐
- 浅谈汇编器、编译器和解释器
- 让我们做个简单的解释器(三)
- 让我们做个简单的解释器(一)
- 用 350 行代码从零开始,将 Lisp 编译成 JavaScript
- 基于JSP编译器基本语法的使用详解
- C#命令行编译器配置方法
- Java虚拟机JVM性能优化(二):编译器
- AngularJS HTML编译器介绍
- 实现接口时@Override注解问题
- 条款06:若不想使用编译器自动生成的函数,就该明确拒绝
- g++编译 参数 .
- 关于 ndk和jni的区别
- vim中的杀手级插件: YouCompleteMe
- Google C++ unit test 在ARM Android 2.3 上的编译与使用
- 从代码示例了解ECMAScript5新特性
- Java的可移植性受到广泛使用
- C++ .H .CPP
- Windows Server 2003远程桌面多用户连接问题
- centos下安装nginx