Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

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

When a constant isn't really a constant

Cyclic class dependencies can create unpredictable runtime behavior

  • Print
  • Feedback

Page 2 of 2

public interface InterfaceA
{
    public static final int A = 2 * InterfaceB.B;
} // End of interface
public interface InterfaceB
{
    public static final int B = InterfaceC.C + 1;
} // End of interface
public interface InterfaceC extends InterfaceA
{
    public static final int C = A + 1;
} // End of interface


Try this version of main():

public class Main implements InterfaceA, InterfaceB, InterfaceC
{
    public static void main (String [] args)
    {
        System.out.println (A + B + C);
    } 
} // End of class


It prints 7. Ignore this value for now and change main() in a seemingly innocuous way:

public class Main implements InterfaceA, InterfaceB, InterfaceC
{
    public static void main (String [] args)
    {
        System.out.println (C + B + A); // The sum is still the same, right?
    } 
} // End of class


The result is now 6. Reordering summation terms seems to have changed the result. Did you expect that? Let's see why this happened.

Although all initializer expressions for fields A, B, and C look like they could be evaluated at compile time, they can't because of the cycle in their definitions. A depends on B; B depends on C; and, C depends on A.

As a result, the compiler will not do any inlining and instead generates static initializer code (see Note 2) that sets all three fields at classloading/initialization time. An important side effect is that every expression containing any of these fields now depends on the order of classloading by the application. To work out the first main() result, I note that InterfaceA is the first interface to load (the summation operands are evaluated left to right), but InterfaceC is the first interface to finish initialization (because of the dependency direction). Field InterfaceC.C depends on InterfaceA.A and is set while InterfaceA's initialization is still pending (hence, field A is still at the default zero value). When all is said and done, C ends up as 1, B as 2, and finally A as 4, which adds up to 7. I leave working out the last main() version as an exercise to readers. (Hint: All three values will end up differing.)

Perhaps my example looks contrived. However, imagine the same three interfaces defined in distinct packages scattered throughout a large code base: noticing the cyclic definition will not be quite so easy then. Each individual definition looks like it could be inlined, and a casual glance will not detect any issues. However, such code causes very obscure problems later on: although some expressions' values will be reproducible between runs of the same version of your application, changing working code can cause a different classloading order and an unpredictably new execution path somewhere. Alternatively, unpredictable changes in concurrent thread scheduling can cause different classloading orders. Unfortunately, most compilers do not consider such code erroneous or at least worthy of a warning to the programmer.

About the author

Vladimir Roubtsov has programmed in a variety of languages for more than 13 years, including Java since 1995. Currently, he develops enterprise software as a senior engineer for Trilogy in Austin, Texas.
  • Print
  • Feedback

Resources