我可以在不破坏向后兼容性的情况下将 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 中定义的,所以它们自动为 staticfinal。)

这些常量在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 类型(比如 IntegerByte)是行不通的,但我相信这对基元来说很好。然而,尽管在网上搜索了一个小时以进行确认,但我仍然有一丝不相信。

此更改向后兼容吗?还是我错了,有没有可能破坏依赖 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 ) 是一个热门候选,但没有设法导致那个错误。