Sunday, April 20, 2003

Here's the code to my previous blog titled "Compiler optimizations may not ...."

ConstantsA.java


package abc;

public interface ConstantsA
{
public static final int CONSTANT_1 = 2003;
public static final String CONSTANT_2 = "Java is good for eBiz";
}


TestConstantA.java

package abc;

public class TestConstantA
{
public static final int CONSTANT_A = 1010;
public static final String CONSTANT_A1 = "Hello_Brave_New_World";
public static final boolean DEBUG = false;

public TestConstantA()
{
System.out.println("TestConstantA's CONSTANT_A : " +
TestConstantA.CONSTANT_A);
System.out.println("TestConstantA's CONSTANT_A1 : " +
TestConstantA.CONSTANT_A1);

if(DEBUG)
{
System.out.println("Debug message. Works only if DEBUG is true");
}
}

public void matchValue(Integer constantA, String constantA1)
{
System.out.println("Value sent by TestConstantB: " + constantA.intValue() +
", Value expected by TestConstantA: " + TestConstantA.CONSTANT_A);
System.out.println("Value sent by TestConstantB: " + constantA1 +
", Value expected by TestConstantA: " + TestConstantA.CONSTANT_A1);
}

public static void main(String[] args)
{
TestConstantA testconstanta = new TestConstantA();
}

}


TestConstantB.java

package def;

import abc.TestConstantA;

import java.lang.reflect.Method;

public class TestConstantB implements abc.ConstantsA
{
public TestConstantB(Integer constantA, String constantA1)
{
try
{

//All this exercise to avoid a direct dependency on
// "import abc.TestConstantA".
//The compiler optimizes the code and replaces all
//Constants with their values.
//If there are no more references to the class being
//imported, it is removed.
Class testConstantAClazz = Class.forName("abc.TestConstantA");
Method matchValueMethod =
testConstantAClazz.getMethod("matchValue",
new Class[] {Integer.class, String.class});

Object testConstantAObj = testConstantAClazz.newInstance();
matchValueMethod.invoke(testConstantAObj,
new Object[] {constantA, constantA1});
}
catch(Exception e)
{
System.out.println("Error occured while using Reflection.");
}

System.out.println("TestConstantA's CONSTANT_A as " +
"perceived by TestConstantB: " +
TestConstantA.CONSTANT_A);
System.out.println("TestConstantA's CONSTANT_A1 as " +
"perceived by TestConstantB: " +
TestConstantA.CONSTANT_A1);

System.out.println("ConstantsA's CONSTANT_1 as " +
"perceived by TestConstantB: " + CONSTANT_1);
System.out.println("ConstantsA's CONSTANT_2 as " +
"perceived by TestConstantB: " + CONSTANT_2);
}

public static void main(String[] args)
{
if(args.length != 2)
{

System.out.println("Usage (Constants will be hardcoded in " +
"compiler code): java def.TestConstantB " +
TestConstantA.CONSTANT_A + " " +
TestConstantA.CONSTANT_A1);
System.exit(1);

}
TestConstantB testconstantb = new TestConstantB(
Integer.valueOf(args[0]), args[1] );
}

}

Friday, April 18, 2003

This is my first tech blog. I had sent this blurb to JavaWorld's Tips 'N Tricks, but got rejected. So, here it is:
Compiler optimizations may not always be welcome, especially if you are working with Constants.

SUMMARY:
Referring to Constants (public static final) contained in other classes can cause issues if you are not careful while building your code.

If you are using constants i.e. public static final references in your code, you must be aware of the fact that the Java compiler optimizes your code by replacing the references with the constants' values. This is done to improve performance. However, there are instances where this behaviour of the compiler can cause strange errors to occur. For example, consider this code snippet:


package def;

import abc.TestConstantA;
//other imports...

public class TestConstantB
{
...
System.out.println("TestConstantA : " + abc.TestConstantA.CONSTANT_A);
System.out.println("TestConstantA : " + abc.TestConstantA.CONSTANT_A1);
...
}


The class in package def imports the class TestConstantA from the package abc and uses the TestConstantA.CONSTANT_A variable. On compiling the two packages, we can decompile the class files by using a Decompiler such as DJ. Doing so, reveals the optimization made by the compiler:


//Decompiled code
package def;

//other imports...

public class TestConstantB
{
...
System.out.println("TestConstantA : 1010");
System.out.println("TestConstantA : Hello_World");
...
}


The Constants have been replaced with their actual values. Even the import abc.TestConstantA; statement has been removed.

Next, comes the interesting part! Change the values of abc.TestConstantA.CONSTANT_A and abc.TestConstantA.CONSTANT_A1to new values and compile only the abc package. You will realize, to your horror that the def.TestConstantB, which we did not recompile with the new abc code, still uses the old abc.TestConstantA values hard-coded! This can happen to you especially if you are using Third-party libraries and when you are not careful with your build process.

Constant substitution during compilation is not as bad as I have made it look like. You can use it to do Conditional compilation like so:


private static final boolean DEBUG = false;
...
if(DEBUG)
{
System.out.println("Debug message. Works only if DEBUG is true");
}


Decompiling the class shows that the if block is absent from the class. This is because the DEBUG evaluates to false and a second optimization knocks off this unreachable if(false){...} block. This reduces unnecessary variable checks and also reduces the bytecode size.

RESOURCES:
Source code:
Should you call a frequently accessed constant directly? : http://www.javaworld.com/javaworld/javaqa/2001-07/03-qa-0720-direct.html
DJ Decompiler: http://members.fortunecity.com/neshkov/dj.html