Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I change constant from int to byte in Java without breaking backward compatibility?

I am submitting a change to JNA which has in previous releases defined a set of constants as int type, specifically:

int VER_EQUAL = 1;
int VER_GREATER = 2;
int VER_GREATER_EQUAL = 3;
... etc...

(Since they are defined in an interface they are automatically static and final.)

These constants are used as the Condition argument in the VerSetConditionMask function, which requires a BYTE argument, mapped in JNA to Java's byte.

To use the existing int constants in that function would require me (and other users) to explicitly typecast them, so I want to change these constant values in the library to be byte, e.g.,

byte VER_EQUAL = 1;
byte VER_GREATER = 2;
byte VER_GREATER_EQUAL = 3;
... etc...

I do not think this change breaks backwards compatibility, because any code using these constants must currently expect at least an int, and will happily widen the byte without complaint. I've written plenty of test code to convince myself of this. I'm aware that changing Object types (like Integer to Byte) would not work, but I believe this is fine for primitives. However, despite an hour searching the web for confirmation, I remain the tiniest bit unconvinced.

Is this change backwards compatible? Or am I wrong and is there a case where this could break existing code relying on the int primitive?

like image 662
Daniel Widdis Avatar asked Jan 07 '19 22:01

Daniel Widdis


People also ask

Is Java always backwards compatible?

Java occasionally breaks backward compatibility, both at the source and at the binary level, when doing so is judged to be more beneficial than not doing so, including in Java 17, which removes RMI Activation (and begins the removal process of the Security Manager).

Is Java 9 backward compatible?

Java 9 introduces a modular system which is moving away from today's monolithic Java SE platform. Backward compatibility is one of the main priorities and the Oracle engineering team has worked on a smooth transition to Java 9.

What is meant by backward compatible in Java?

Backward compatible (also known as downward compatible or backward compatibility) refers to a hardware or software system that can successfully use interfaces and data from earlier versions of the system or with other systems.


2 Answers

Maybe it's obvious. But maybe not. If some third party user fetched the int values and stored them locally in Integer objects, changing the int to byte will cause a compile time error there:

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;
    }
}

Beyond that, I think that one should at least mention reflection for completeness.

I also considered the ternary operator (https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.25.2 ) as a hot candidate, but did not manage to cause an error with that one.

like image 101
Marco13 Avatar answered Oct 20 '22 21:10

Marco13


Is this change backwards compatible?

No.

Even though constants are normally inlined, the field is still a part of the class file, so it is possible that some dynamic lookup references the field using it's old type which is int. For instance:

// 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
}

This throws a NoSuchFieldException since the lookup is looking for the old type of the field.

This also goes for byte code writing APIs that access the field, e.g. with 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

The above code works and prints 10 if the field is of type int, but fails once it is changed to byte.

like image 37
Jorn Vernee Avatar answered Oct 20 '22 21:10

Jorn Vernee