Final Constants

Final constants are a good place to start, since many of you are already familiar with the concept. Consider the code in Example 2-1.

Example 2-1. A class that doesn't use constants
package oracle.hcj.finalstory;
public class FinalConstants {
 public static class CircleTools {
 public double getCircleArea(final double radius) {
 return (Math.pow(radius, 2) * 3.141);
 }
 public double getCircleCircumference(final double radius) {
 return ((radius * 2) * 3.141);
 }
 public double getCircleExtrudedVolume(final double radius, final double height) {
 return ((radius * 2 * height) * 3.141);
 }
 }
}


The problem with this code is that the developer has to change all three instances of the value 3.141, his estimate for p, in all three methods if he wants to make his calculations more precise. Seasoned developers will see the opportunity for a class-scoped constant, as seen in Example 2-2.

Example 2-2. Simple constants using final
package oracle.hcj.finalstory;
public class FinalConstants {
 public static class CircleToolsBetter {
 /** A value for PI. **/
 public final static double PI = 3.141;
 public double getCircleArea(final double radius) {
 return (Math.pow(radius, 2) * PI);
 }
 public double getCircleCircumference(final double radius) {
 return ((radius * 2) * PI);
 }
 public double getCircleExtrudedVolume(final double radius, final double height) {
 return ((radius * 2 * height) * PI);
 }
 }
}


This code is much better. Now the developer can change the constant, and this one change will propagate throughout the class. The reason I am beating this particular dead horse is because there are some traps involving constants that trip up even experienced developers.

Public Primitives and Substitution

The first of these traps involves public primitive constants that are used by other code. Because primitive finals are substituted at compile time with their values, if you change a final that is used by other classes, you must remember to recompile those other classes or your change will not take effect. The same rule applies to constants of type java.lang.String. Although String is a constructed type, it is also substituted at compile time. All constructed types other than String, mutable or not, are not substituted at compile time. To understand how this works, look at Example 2-3.

Example 2-3. Various final variables
package oracle.hcj.finalstory;
public class FinalReplacement {
 /** A string constant */
 public final static String A_STRING = "Hardcore Java";
 /** An int constant. */
 public final static int AN_INT = 5; /** A double constant. */
 public final static double A_DOUBLE = 102.55d;
 /** An array constant. */
 public final static int[] AN_ARRAY = new int[] {1, 2, 3, 6, 9, 18, 36};
 /** A color constant. */
 public final static Color A_COLOR = new Color(45, 0, 155);
 public void someMethod( ) {
 System.out.println(A_STRING);
 System.out.println(AN_INT);
 System.out.println(A_DOUBLE);
 System.out.println(AN_ARRAY);
 System.out.println(A_COLOR);
 }
}


Once the compiler sees code such as this, it starts substituting out the primitives and String objects. After the first pass of the compiler, the class will look something like this:

package oracle.hcj.finalstory;
public class FinalReplacement {
 /** A string constant */
 public final static String A_STRING = "Hardcore Java";
 /** An int constant. */
 public final static int AN_INT = 5; /** A double constant. */
 public final static double A_DOUBLE = 102.55d;
 /** An array constant. */
 public final static int[] AN_ARRAY = new int[] {1, 2, 3, 6, 9, 18, 36};
 /** A color constant. */
 public final static Color A_COLOR = new Color(45, 0, 155);
 public void someMethod( ) {
 System.out.println("Hardcore Java");
 System.out.println(5);
 System.out.println(102.55d);
 System.out.println(AN_ARRAY);
 System.out.println(A_COLOR);
 }
}


Screenshot

The compiler will concatenate consecutive String literals to form one literal. Therefore, the following two lines are identical from the point of view of the compiler:

public final static String A_STRING = "Hardcore Java";
public final static String A_STRING = "Hardcore"+ "Java";


Both of these lines would result in an identical declaration that is a string constant. Also, this optimization technique applies to where there are consecutive string literals in your code.


The primitive and String constants were substituted while the other constructed types were left as variables. Since this code is all in one class, if you change a constant, you have to recompile this class anyway. However, if another class (for example, ExternalUser) is using the constant A_STRING and you change it in FinalReplacement, you have a problem. ExternalUser will have to be recompiled to trigger a resubstitution using the new A_STRING value, but the Java compiler will not notice this dependency. Here's a simple version of ExternalUser:

package oracle.hcj.finalstory;
public class ExternalUser {
 public static void main(String[] args) {
 System.out.println("The title of the tutorial is: " +
 FinalReplacement.A_STRING + ".");
 }
}


This extremely simple class uses the A_STRING constant from the FinalReplacement class. If you run the main( ) method, the output will look like the following:

>ant -Dexample=oracle.hcj.finalstory.ExternalUser run_example run_example:
 [java] The title of the tutorial is: Hardcore Java.


Now change the value of A_STRING to "Java Hardcore" in the FinalReplacement class:

/** A string constant */
 public final static String A_STRING = "Java Hardcore";


Recompile FinalReplacement using the following command:

>ant -Dexample=oracle/hcj/finalstory/FinalReplacement.java compile_example compile_example:
 [javac] Compiling 1 source file to C:\dev\hcj\bin


Now run the ExternalUser example again:

>ant -Dexample=oracle.hcj.finalstory.ExternalUser run_example run_example:
 [java] The title of the tutorial is: Hardcore Java.


There is no change despite the change in the A_STRING constant. To fix this problem, recompile the ExternalUser class:

>ant -Dexample=oracle/hcj/finalstory/ExternalUser.java compile_example compile_example:
 [javac] Compiling 1 source file to C:\dev\hcj\bin


Running the example once more gives you the output you were seeking when you changed A_STRING in FinalReplacement:

>ant -Dexample=oracle.hcj.finalstory.ExternalUser run_example run_example:
 [java] The title of the tutorial is: Java Hardcore.


The Java compiler doesn't automatically notice the dependency between the String and primitive constants and their users. Some build environments recognize this dependency automatically but don't depend on it. In fact, whenever you change a public primitive constant, it's a good idea to simply rebuild the whole project, just to be safe. If your code is also being used by other projects, make sure you put the change in your release notes so that others will know to recompile their projects.

Screenshot

During a project for the aerospace industry, my consulting company changed a primitive public constant and forgot to change the release notes. We promptly broke a customer's code and only hours later did we figure out that they needed to rebuild all 400 of their classes to get it working.


Excessive Constants

The overuse of class-scoped constants is another common trap that can clutter up otherwise well-built code. For example, consider a situation in which there are many constants in a mathematically oriented class, as shown in Example 2-4.

Example 2-4. Excessive use of private constants
package oracle.hcj.finalstory;
public class FinalConstants {
 public class SomeClass {
 /** Contains the constant for the first equation. */
 private static final double K1 = 3.141;
 /** Contains the offset for the first equation. */
 private static final double X1 = 15.0;
 /** Contains the constant for the second equation. */
 private static final double K2 = 1.414;
 /** Contains the offset for the second equation. */
 private static final double X2 = 45.0;
 /** Contains a constant for both equations. */
 private static final double M = 9.3;
 public double equation1(final double inputValue) {
 return (((Math.pow(inputValue, 2.0d) / K1) + X1) / M);
 }
 public double equation2(final double inputValue) {
 return (((Math.pow(inputValue, 3.0d) * K2) + X2) * M);
 }
 }
}


Although there is nothing technically wrong with this code, it is a rather nasty mess. If your equations become large, with multiple constants and numerous terms, the situation turns into something more appropriate for a horror movie. To avoid this trap, rewrite this disaster:

package oracle.hcj.finalstory;
public class FinalConstants {
 public class SomeClassBetter {
 /** Contains a constant for both equations. */
 private static final double M = 9.3;
 public double equation1(final double inputValue) {
 final double K = 3.141;
 final double X = 15.0;
 return (((Math.pow(inputValue, 2.0d) / K) + X) / M);
 }
 public double equation2(final double inputValue) {
 final double K = 1.414;
 final double X = 45.0;
 return (((Math.pow(inputValue, 3.0d) * K) + X) * M);
 }
 }
}


Although the method-scoped final variables may look strange, they are quite legal and useful. For the compiler, the semantics for method-scoped constants are the same as those for class-scoped constants. The compiler will replace primitive and String constant variables with the value of the variable at compile time. In fact, since the constants in Example 2-4 are private, you know that they cannot be accessed outside of the class, so there is no reason to leave them in the scope of the class. In the process of making your code easier to read and understand, you removed the need for those silly 1s and 2s in your constant names. Since the constants are used only in those methods, it is appropriate to restrict them to the methods in which they are used.

Take Advantage of Warning Options in Good Development Tools

Good tools can really help you to identify private variables and mistakes associates with them. For example, my IDE is configured to warn me if a private member of a class is not used in that class. Not only does this help me find private constants that should have been declared public, it also helps me clean up code that has been developed for a while and may have accumulated some lint throughout the process. Eclipse, for example, has many of these options; you should turn on all of them to warning level. Using these warnings can save you a lot of debugging time down the road.


To summarize, if you have a private final static that is used only in one method, you should probably move it into that method. On the other hand, if the constants are being used in more than one method, you should leave them as class-scoped. The constant M was not moved because it was being used by two of the methods.

      
Comments