您的位置:首页 > 其它

从零开始开发JVM语言(十二)重载方法的选择

2016-06-30 00:00 295 查看
摘要: 编译器 语义分析

目录戳这里

方法重载在面向对象里很基础,不管是学校还是自学时都会教到这个概念。实际上相互重载的方法除了名字相同之外,并没有任何关系,因为它们拥有完全不同的方法签名。

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  编译器 语义分析