我可以在不破坏向后兼容性的情况下将 Java 中的常量从 int 更改为 byte 吗?
Can I change constant from int to byte in Java without breaking backward compatibility?
我正在提交一个 change to JNA,它在以前的版本中定义了一组常量作为 int
类型,具体来说:
int VER_EQUAL = 1;
int VER_GREATER = 2;
int VER_GREATER_EQUAL = 3;
... etc...
(因为它们是在 interface
中定义的,所以它们自动为 static
和 final
。)
这些常量在VerSetConditionMask函数中用作Condition
参数,需要一个BYTE
参数,在JNA中映射到Java的byte
.
要在该函数中使用现有的 int
常量需要我(和其他用户)明确地对它们进行类型转换,所以我想将库中的这些常量值更改为 byte
,例如,
byte VER_EQUAL = 1;
byte VER_GREATER = 2;
byte VER_GREATER_EQUAL = 3;
... etc...
我认为此更改不会破坏向后兼容性,因为使用这些常量的任何代码当前必须至少期望 int
,并且会毫无怨言地愉快地扩大 byte
。我写了很多测试代码来说服自己。我知道改变 Object
类型(比如 Integer
到 Byte
)是行不通的,但我相信这对基元来说很好。然而,尽管在网上搜索了一个小时以进行确认,但我仍然有一丝不相信。
此更改向后兼容吗?还是我错了,有没有可能破坏依赖 int
原语的现有代码的情况?
Is this change backwards compatible?
没有
即使常量通常是内联的,该字段仍然是 class 文件的一部分,因此某些动态查找可能会使用旧类型 int
引用该字段。例如:
// defined in `MyClass`
static final byte x = 10;
public static void main(String[] args) throws Throwable {
lookup().findStaticGetter(MyClass.class, "x", int.class); // old code
}
这会引发 NoSuchFieldException
,因为查找正在查找字段的旧类型。
这也适用于字节码编写访问该字段的 API,例如ASM:
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cw.visit(55, ACC_PUBLIC, "Test", null, "java/lang/Object", null);
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "m", "()V", null, null);
mv.visitCode();
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitFieldInsn(GETSTATIC, "MyClass", "x", "I"); // looking for int field
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
Class<?> cls = lookup().defineClass(cw.toByteArray());
cls.getMethod("m").invoke(null); // NoSuchFieldError
如果字段的类型为 int
,上面的代码可以工作并打印 10
,但是一旦更改为 byte
.
就会失败
也许这是显而易见的。但也许不是。如果某些第三方用户获取 int
值并将它们本地存储在 Integer
对象中,将 int
更改为 byte
将导致编译时错误:
interface IntToByteWithInt
{
int VER_EQUAL = 1;
int VER_GREATER = 2;
int VER_GREATER_EQUAL = 3;
}
interface IntToByteWithByte
{
byte VER_EQUAL = 1;
byte VER_GREATER = 2;
byte VER_GREATER_EQUAL = 3;
}
public class IntToByte
{
public static void main(String[] args)
{
Integer a = IntToByteWithInt.VER_EQUAL;
// Type mismatch: cannot convert from byte to Integer
Integer b = IntToByteWithByte.VER_EQUAL;
}
}
除此之外,我认为至少应该提及 反射 以确保完整性。
我也认为三元运算符 (https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.25.2 ) 是一个热门候选,但没有设法导致那个错误。
我正在提交一个 change to JNA,它在以前的版本中定义了一组常量作为 int
类型,具体来说:
int VER_EQUAL = 1;
int VER_GREATER = 2;
int VER_GREATER_EQUAL = 3;
... etc...
(因为它们是在 interface
中定义的,所以它们自动为 static
和 final
。)
这些常量在VerSetConditionMask函数中用作Condition
参数,需要一个BYTE
参数,在JNA中映射到Java的byte
.
要在该函数中使用现有的 int
常量需要我(和其他用户)明确地对它们进行类型转换,所以我想将库中的这些常量值更改为 byte
,例如,
byte VER_EQUAL = 1;
byte VER_GREATER = 2;
byte VER_GREATER_EQUAL = 3;
... etc...
我认为此更改不会破坏向后兼容性,因为使用这些常量的任何代码当前必须至少期望 int
,并且会毫无怨言地愉快地扩大 byte
。我写了很多测试代码来说服自己。我知道改变 Object
类型(比如 Integer
到 Byte
)是行不通的,但我相信这对基元来说很好。然而,尽管在网上搜索了一个小时以进行确认,但我仍然有一丝不相信。
此更改向后兼容吗?还是我错了,有没有可能破坏依赖 int
原语的现有代码的情况?
Is this change backwards compatible?
没有
即使常量通常是内联的,该字段仍然是 class 文件的一部分,因此某些动态查找可能会使用旧类型 int
引用该字段。例如:
// defined in `MyClass`
static final byte x = 10;
public static void main(String[] args) throws Throwable {
lookup().findStaticGetter(MyClass.class, "x", int.class); // old code
}
这会引发 NoSuchFieldException
,因为查找正在查找字段的旧类型。
这也适用于字节码编写访问该字段的 API,例如ASM:
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cw.visit(55, ACC_PUBLIC, "Test", null, "java/lang/Object", null);
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "m", "()V", null, null);
mv.visitCode();
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitFieldInsn(GETSTATIC, "MyClass", "x", "I"); // looking for int field
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
Class<?> cls = lookup().defineClass(cw.toByteArray());
cls.getMethod("m").invoke(null); // NoSuchFieldError
如果字段的类型为 int
,上面的代码可以工作并打印 10
,但是一旦更改为 byte
.
也许这是显而易见的。但也许不是。如果某些第三方用户获取 int
值并将它们本地存储在 Integer
对象中,将 int
更改为 byte
将导致编译时错误:
interface IntToByteWithInt
{
int VER_EQUAL = 1;
int VER_GREATER = 2;
int VER_GREATER_EQUAL = 3;
}
interface IntToByteWithByte
{
byte VER_EQUAL = 1;
byte VER_GREATER = 2;
byte VER_GREATER_EQUAL = 3;
}
public class IntToByte
{
public static void main(String[] args)
{
Integer a = IntToByteWithInt.VER_EQUAL;
// Type mismatch: cannot convert from byte to Integer
Integer b = IntToByteWithByte.VER_EQUAL;
}
}
除此之外,我认为至少应该提及 反射 以确保完整性。
我也认为三元运算符 (https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.25.2 ) 是一个热门候选,但没有设法导致那个错误。