是否可以在不破坏向后兼容性的情况下在Java中将常量从int更改为byte?

时间:2019-01-07 22:45:00

标签: java casting backwards-compatibility primitive-types

我正在向change提交一个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类型(例如从Integer更改为Byte)是行不通的,但是我相信这对于基元是很好的。但是,尽管在网上搜索了一个小时进行确认,但我仍然不敢置信。

此更改是否向后兼容?还是我做错了,有没有可能依靠int原语破坏现有代码?

2 个答案:

答案 0 :(得分:4)

也许很明显。但也许不是。如果某些第三方用户获取了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;
    }
}

除此之外,我认为至少应该提及 reflection 以确保完整性。

我也认为三元运算符(https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.25.2)是最热门的候选者,但是并没有导致那个错误。

答案 1 :(得分:3)

  

此更改是否向后兼容?

否。

即使通常内联常量,该字段仍然是类文件的一部分,因此某些动态查找可能会使用其旧类型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

如果该字段的类型为10,则上面的代码可以工作并打印int,但是一旦更改为byte,则失败。