Java:静态完成方法名称/签名解析(编译时)?

时间:2012-02-10 06:56:02

标签: java compiler-construction compilation

我今天遇到了一个有趣的问题,我认为这在Java中是不可能的。我针对jgroups的2.6版本编译了我的java代码,但是在运行时使用了版本2.12(tomcat web app部署)。我收到以下错误

org.jgroups.Message.<init>(Lorg/jgroups/Address;Lorg/jgroups/Address;Ljava/io/Serializable;)

假设从那时起API会发生变化,我想把我的代码移植到jgroups-2.12,但令我惊讶的是,用jgroups-2.12编译的代码很好,当我更换新jar时(不改变单行)我的代码,只是编译jgroups-2.12而不是jgroups-2.6),它工作得很好。

我后来意识到2.6中的构造函数Message(Address, Address, Serializable)在2.12中被更改为Message(Address,Address,Object)。这意味着在运行时,JVM试图找到完全相同的方法,但未能这样做。

这是否意味着Java编译器在编译时嵌入了确切的方法名称和精确的参数,而具有更广泛参数的方法将不起作用?

3 个答案:

答案 0 :(得分:4)

是的,这是完全正确的 - 确切的签名在编译时被绑定,这就是字节码中包含的内容。

实际上,这甚至包括返回类型,它不包含在重载等目的的签名中。

从根本上说,如果您更改现有公共API成员的任何内容,那将是一个重大变化。您可以使用某些仅限语言的更改,例如将String[]参数更改为String...参数,或引入泛型(如果擦除兼容,则在某些情况下)使用前面的代码),但这就是它。

Java语言规范的

Chapter 13完全与二进制兼容性有关 - 请阅读以获取更多信息。但特别是来自section 13.4.14

  

更改方法或构造函数的形式参数的名称不会影响预先存在的二进制文件。更改方法的名称,方法或构造函数的形式参数的类型,或者从方法或构造函数声明添加参数或从参数或构造函数声明中删除参数会创建具有新签名的方法或构造函数,并具有以下组合的效果:使用旧签名删除方法或构造函数,并使用新签名添加方法或构造函数(参见§13.4.12)。

答案 1 :(得分:4)

  

这是否意味着Java编译器在编译时嵌入了确切的方法名称和精确的参数,而具有更广泛参数的方法将不起作用?

完全。您也可以从您收到的错误消息中看到这一点:

org.jgroups.Message.<init>(Lorg/jgroups/Address;Lorg/jgroups/Address;Ljava/io/Serializable;)

此处包含完整的签名,运行时会查找完美匹配。

在更改API时,还有其他几种情况会破坏Java中的二进制兼容性,但不会破坏源兼容性,例如,当您将基元类型更改为其盒装变体时,反之亦然。正如Jon所指出的,只有Generics中的更改(但不是所有更改)和使用VarArgs语法都不会影响运行时方法的解析,因为两者都只是编译器功能,不会影响字节码。

这也意味着当您在新库版本中引入方法的重载时,此重载将仅由使用新版本编译的调用方使用。旧的二进制文件仍将调用旧方法,即使它们的参数类型更适合新的重载。

对于库设计者来说,有时建议不要更改现有方法的签名,而只是添加新的重载(并将旧方法转发给新方法,以便调用哪个方法无关紧要)。当然,缺点是所有这些重载都会掩盖真正的API,并使理解API变得更加困难。

答案 2 :(得分:1)

Java编译器必须在编译文件中放入确切的方法名称和精确参数,以便稍后确定要加载哪个类以及调用哪个方法。除此之外,没有办法精确调用所请求的方法。