The Java static compiler (javac) inlines some static final variables and brings the values directly to the constant pool. Consider the following example. Class A defines some constants (public static final variables):
public class A {
public static final int INT_VALUE = 1000;
public static final String STRING_VALUE = "foo";
}
Class B uses these constants:
public class B {
public static void main(String[] args) {
int i = A.INT_VALUE;
System.out.println(i);
String s = A.STRING_VALUE;
System.out.println(s);
}
}
When you compile class B, javac gets the values of these constants from class A and inlines these values in B.class. As a result, the dependency B had to class A at the compile time is erased from the bytecode. This is a rather peculiar behavior because you are baking in the values of these constants at the time of compilation. And you would think that this is one of the easiest things that the JIT compiler can do at runtime.
Is there any way or any hidden compiler option that lets you disable this inlining behavior of javac? For the background, we're looking into doing bytecode analysis for dependency purposes, and it is one of the few cases where bytecode analysis fails to detect compile-time dependencies. Thanks!
Edit: this is a vexing issue because normally we don't control all the source (e.g. third-party libraries that define constants). We're interested in detecting these dependencies from the perspective of using the constants. Since the reference is erased from the code that uses the constants, there is no easy way to detect them, short of doing source code analysis.
I feel java tightly relies on dynamic compilation and it doesn't do any fancy compilation logic as like C++.
you can try out some options with JIT compilers which does run-time optimization which may have some options to disable/enable this.
in default javac you may not get that option. you have to use 1. some type of dependency graph like extends or implements 2. using method based linking.
-s
I think this is a serious bug. Java is not C/C++. There is a principle(or not) "Compile once, run everywhere".
In this case, when Class A is changed. Any Classes referencing A.CONST_VALUE must be re-compiled and they hardly know whether Class A is changed.
I don't believe so. The simplest workaround would be to expose these as properties rather than fields:
Don't forget that in certain cases the inlining is essential to the use of the value - for example, if you were to use
INT_VALUE
as a case in a switch block, that has to be specified as a constant value.I recently came across a similar issue, and, as it was said above, such inlining can be worked around using non-compile-time expressions, say:
where the
constOf
method family is merely:This is a bit shorter than other suggestions like
Integer.valueOf(1000).intValue()
ornull!=null?0: 1000
jmake is an open-source project that claims to do the whole job of keeping track of dependencies between Java files and incrementally compiling the minimum set of files required. It claims to correctly handle changes to static final constants, albeit by sometimes requiring the whole project to be recompiled. It even handles changes at a finer granularity than classfiles; if (for example) the signature of a method C.m() changes, then it only recompiles the classes that actually depend on m() rather than all classes that use C.
DISCLAIMER: I have no experience using jmake.
Item 93 of Java Puzzlers (Joshua Bloch) says that you can work round this by preventing the final value from being considered a constant. For example:
Of course none of this is relevant if you don't have access to the code that defines the constants.