Two days ago we discussed the impact of removing or changing a constant (i.e. public static final variable) from a Java class within an API. Most of the developers knew, that at least some of the constants were inlined into the bytecode at compile time. Some senior ones even stated, that removing the exported constant will not have an impact on the running system. Let’s have a look what the Java language specification defines and how it is really working.
References to fields that are constant variables (§4.12.4) are resolved at compile-time to the constant value that is denoted. No reference to such a field should be present in the code in a binary file (except in the class or interface containing the field, which will have code to initialize it). [JSL 13.1]
Constant variables are defined as variable of primitive type or type String that are final and initialized with a compile-time constant expression [JSL 4.12.4]. Whether an expression is compile-time constant or not is defined on two pages under [JLS 15.28]. Basically it is if composed only of operators like +, -, ~, !, <<, >>, <, <=, >, >=, == !=, etc.
Lets see the result of this definition in an example. The following class defines a few constants:
public class Constants {
public static final int A = 32767; //int constant (inlined as sipush)
public static final int B = 32768; //int constant (inlined as ldc)
public static final String C = "Hello " + "World"; //String constant with operators (inlined)
public static final Class<?> D = String.class; //Class constant (not inlined)
public enum Direction{
NORTH, EAST, WEST, SOUTH; //Enum constant (not inlined)
}
}
This constants were referenced by the following client code:
public class Client {
public static void main(String[] args) {
int a = Constants.A;
int b = Constants.B;
String c = Constants.C;
Class<?> d = Constants.D;
Constants.Direction direction = Constants.Direction.SOUTH;
}
}
Within the bytecode (javap -p -v Client) we can determine the following things:
- Integer constants with a value between -32768 and 32767 will not result in an entry in the constant pool. Instead sipush is used for better performance (see line 1).
- Integer constants with a value outside of this scope result in an entry in the constant pool and were loaded with the command ldc (see line 3).
- String constants also result in an entry in the constant pool and were loaded using ldc (line 5).
- The constant referencing a class object is not a compile-time expression and therefore the value is loaded at runtime using getstatic (line 7).
- The same for enums, because enums are normal public static final variables of no compile-time constant, they are not inlined into the client classes constant pool (line 9).
0: sipush 32767
3: istore_1
4: ldc #16; //int 32768
6: istore_2
7: ldc #17; //String Hello World
9: astore_3
10: getstatic #19; //Field Constants.D:Ljava/lang/Class;
13: astore 4
15: getstatic #25; //Field Constants$Direction.SOUTH:LConstants$Direction;
18: astore 5
20: return