Recommended: Sing it, brah! 5 fabulous songs for developers
JW's Top 5
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
March 28, 2003
What are the possible negative effects of using cyclic definitions in Java?
Normally, a Java compiler produces very "dynamic" output: you can recompile just a single class, and the rest of the application
picks up the changes. This happens because the .class format uses dynamic links to reference cross-class constructs such as fields and methods, and they are resolved only at classloading
time.
In this article, I describe a well-known exception to this behavior and an exception to the exception that is fairly rare but can cause very subtle and hard-to-see errors. Cyclic field definitions cause the latter exception, which leads to somewhat nondeterministic execution in Java.
Consider the following two definitions:
public class Main implements InterfaceA
{
public static void main (String [] args)
{
System.out.println (A);
}
} // End of class
public interface InterfaceA
{
public static final int A = 1;
} // End of interface
According to the Java Language Specification, any static final field initialized with an expression that can be evaluated
at compile time must be compiled to byte code that "inlines" the field value. That is, no dynamic link will be present inside
class Main telling it to obtain the value for A from InterfaceA at runtime. Instead, the literal 1 will compile into Main.main() directly. You can confirm this by examining the javap dump for the method:
Method void main(java.lang.String[]) 0 getstatic #23 <Field java.io.PrintStream out> 3 iconst_1 4 invokevirtual #29 <Method void println(int)> 7 return
The iconst_1 instruction above pushes integer value 1 to the JVM operand stack before invoking System.out.println() on it. The value is embedded into byte code, and no link to Interface.A remains. If you recompile InterfaceA.java so that field A is now, say, 2, but do not recompile Main.java, Main.main()'s output stays the same.
The above is well known to any experienced Java programmer. This slightly odd Java feature calls for extra attention when using any kind of Java make tool that can incrementally recompile Java sources based on file modification timestamps only (see Note 1). On the other hand, this feature is sometimes handy for implementing a poor man's version of conditional compilation in Java.
Note that for constant inlining to happen, several conditions must take place simultaneously. For example, it won't occur if the field initializer expression can be evaluated only at runtime:
public interface InterfaceA
{
public static final int A = new java.util.Random ().nextInt ();
} // End of interface
With this change in InterfaceA, the byte code for Main.main() becomes:
Method void main(java.lang.String[]) 0 getstatic #27 <Field java.io.PrintStream out> 3 getstatic #31 <Field int A> 6 invokevirtual #37 <Method void println(int)> 9 return
Observe the now dynamic reference to the field A value in the byte code.
The above change in Interface.A was quite conspicuous. However, imagine instead that the application uses the following three interfaces:
.class format is extendible, and it is possible to preserve all compilation dependencies between classes via a new .class attribute, including dependencies through static final constants. Rumor has it this is precisely what Sun is considering
for future Java versions, although I have no data about whether it will happen in Java 1.5.
static final and the justification for the current behavior