|
|
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
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.
.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