[英]Can I change a return type to be a strict subtype and retain binary compatibility?
如果我有一些课程:
import java.util.Date;
public final class Foo {
private Date date;
public Date getDate(){ return date; }
}
如果我将它编译为二进制文件,而其他人已经针对它构建了代码,那么我是否可以在不破坏二进制兼容性的情况下执行此操作?
import java.sql.Date;
public final class Foo {
private Date date;
public Date getDate(){ return date; }
}
请注意, java.sql.Date
是java.util.Date
的子类。
对我来说很明显,如果我没有将类声明为 final,那么我就会破坏源兼容性(即,之前有人可能已经针对我的库编译了Foo
的子类,它覆盖了getDate
方法以返回java.util.Date
;该代码将不再针对我的最新版本进行编译)。 但是破坏源代码兼容性是否意味着二进制兼容性也被破坏了? (这在其他语言中不成立,例如 scala)
我测试了这个。
不幸的是,当方法的返回类型更改为原始类型的子类型时,在调用点会出现NoSuchMethodError
异常。
因此,进行此更改不是二进制向后兼容的。
这太糟糕了...
test
├── c
├── c_int
│ └── C.java
├── c_num
│ └── C.java
└── Test.java
test/c_num/C.java
:
package test.c;
public class C {
public Number f() {
System.out.println("f Number");
return 0.0;
}
}
test/c_int/C.java
:
package test.c;
public class C {
public Integer f() {
System.out.println("f Integer");
return 1;
}
}
test/Test.java
:
package test;
import test.c.C;
public class Test {
public static void main(String[] args) throws Exception {
C b = new C();
Number n = b.f();
System.out.println(n);
}
}
用不同的返回类型编译两个C
类:
$ javac test/c_int/C.java
$ javac test/c_num/C.java
使用返回类型Number
对C
进行编译Test
:
$ cp test/c_num/C.class test/c/
$ javac test/Test.java
使用返回类型Number
对C
运行Test
:
$ java test.Test
f Number
0.0
使用返回类型Integer
对C
运行Test
,无需重新编译Test
:
$ cp test/c_int/C.class test/c/
$ java test.Test
Exception in thread "main" java.lang.NoSuchMethodError: 'java.lang.Number test.c.C.f()'
at test.Test.main(Test.java:8)
我们还可以在字节码中看到对f
的方法调用包含了该方法的返回类型:
$ javap -c test/Test.class
Compiled from "Test.java"
public class test.Test {
public static void main(java.lang.String[]);
Code:
...
9: invokevirtual #4 // Method test/c/C.f:()Ljava/lang/Number;
...
}
Eclipse 基金会维护一个名为Evolving Java-based APIs的文档。
该文档说明了有关二进制兼容性的内容:
Evolving API classes - API methods and constructors
....
Change result type (including void) - Breaks compatibility
这对仅缩小返回类型的更改也不例外。 对返回类型的所有更改都会破坏二进制兼容性。
我认为最好的方法是自己测试一下。 以下是官方文档的一些片段:
13.4.15. 方法结果类型
更改方法的结果类型,或用 void 替换结果类型,或用结果类型替换 void,具有删除旧方法并添加具有新结果类型或新的 void 结果的新方法的综合效果(参见 § 13.4.12)。
和
13.4.12. 方法和构造函数声明
从类中删除方法或构造函数可能会破坏与引用此方法或构造函数的任何预先存在的二进制文件的兼容性; 当链接来自预先存在的二进制文件的此类引用时,可能会抛出 NoSuchMethodError 。 只有在超类中没有声明具有匹配签名和返回类型的方法时,才会发生这样的错误。
我读这个的方式是,如果您更改返回类型,无论它是否是旧类型的子类型,都意味着您正在删除旧方法并添加新方法,并且根据13.4.12它可以破坏与任何引用此方法或构造函数的预先存在的二进制文件。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.