8

有一个 jar 在创建时应该使用方法MyClass.doSomething(List)。此方法已更改为doSomething(Collection)并放入另一个 jar(仅限此类)。

我将我的第二个 jar 放在类路径中的第一个 jar 前面,但是当我的第一个 jar 中的代码MyClass.doSomething()使用 List 调用时,我仍然得到一个

java.lang.NoSuchMethodError: MyClass.doSomething(Ljava/util/List;)Ljava/util/List;

这怎么可能?Ant 已被用于编译 jars。

4

2 回答 2

12

源代码兼容性和二进制兼容性之间有一个重要区别。

  • 如果某个类 V1 和 V2 的两个版本是二进制兼容的,这意味着针对 V1 编译的类在 V2 上运行得很好。
  • 如果某个类 V1 和 V2 的两个版本是源代码兼容的,这意味着可以针对 V1 编译的类将针对 V2 编译得很好。

正如您所经历的那样,源代码兼容性并不自动意味着二进制兼容性。

编译源代码时,要调用的具体方法签名由编译器决定并存储在.class文件中(在本例中为doSomething(List))。

如果更改了类并在添加时doSomething(List)删除了方法doSomething(Collection),则保留了源代码兼容性(因为您可以简单地针对新类编译相同的代码),但二进制兼容性会丢失!

Java 语言规范有一整节是关于二进制兼容性的

总结一下:虽然将方法的参数类型更改为更通用的类型(通常)是源兼容的,但它不是二进制兼容的。

如果要保留二进制兼容性,则更改必须如下所示:

public void doSomething(Collection foo) { ... } // original method with changed argument type

public void doSomething(List foo) { // new binary compatibility method, just delegates to original one
  doSomething((Collection) foo);
}
于 2011-05-12T11:38:07.663 回答
4

一般来说,如果你得到一个NoSuchMethodError,这意味着在运行时使用的目标类的版本与编译调用类的目标类的版本不同。在您的情况下,这不是一个令人惊讶的错误;第一个 JAR 中的字节码仍然是针对存在采用 a 的方法进行编译的,而该方法List存在

这可能是由于 Ant 的增量编译没有注意到依赖类发生了变化,或者类似的事情。

如果您对整个项目进行干净的重建(嗯,至少您提到的两个 JAR)这个问题应该得到解决,因为编译器会创建调用新doSomething签名的字节码。

于 2011-05-12T11:37:26.927 回答