The Final (Constants) Story
(Page 1 of 7 )
Generally, it is better to swap a logic error for a compiler error. Compiler errors can be found quickly and fixed just as quickly, whereas logic errors can take thouands of man-hours to find and fix. This article explains how to use the Java keyword final to change logic errors into compiler errors, thus saving you an enormous amount of time. It is excerpted from chapter two of
Hardcore Java, written by Robert Simmons Jr. (O'Reilly, 2004; ISBN: 0596005687).
One fundamental principle of programming is that, generally, it is best to swap a logic error for a compiler error. Compiler errors tend to be found in seconds and are corrected just as fast. Syntax errors are a good example. A missing semicolon can make things confusing. If the compiler error is something particularly cryptic, the resolution may take as long as a couple of minutes to discover.
Logic errors, on the other hand, are the bane of all programmers. They hide and hate to reveal themselves. Logic errors seem to have minds of their own, constantly evading detection and dodging your efforts to pin down their cause. They can easily take a thousand times more effort to solve than the worst compiler errors. Worst of all, many logic errors are not found at all and occur only intermittently in sensitive places, which causes your customers to scream for a fix. Logic errors often require you to throw thousands of man-hours at them, only to finally discover that they are minor typos.
The Java keywordfinalcan be instrumental in turning thousands of logic errors into compiler errors without too much effort. With some training in coding standards and some code retrofitting, you can save an enormous amount of man-hours that are better spent elsewhere. Also, you can save your support departments from having to deal with irate customers.
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 oreilly.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 value3.141, his estimate for π, 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 oreilly.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 getCircleArea(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 oreilly.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 andString objects. After the first pass of the compiler, the class will look something like this:
package oreilly.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);
}
}
The compiler will concatenate consecutiveStringliterals 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 andStringconstants 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 constantA_STRINGand you change it inFinalReplacement, you have a problem.ExternalUser will have to be recompiled to trigger a resubstitution using the newA_STRINGvalue, but the Java compiler will not notice this dependency. Here’s a simple version ofExternalUser:
package oreilly.hcj.finalstory;
public class ExternalUser {
public static void main(String[] args) {
System.out.println("The title of the book is: " +
FinalReplacement.A_STRING+ ".");
}
}
This extremely simple class uses theA_STRINGconstant from theFinalReplacementclass. If you run themain()method, the output will look like the following:
>ant -Dexample=oreilly.hcj.finalstory.ExternalUser run_example
run_example:
[java] The title of the book is: Hardcore Java.
Now change the value ofA_STRINGto"Java Hardcore"in theFinalReplacementclass:
/** A string constant */
public final static String A_STRING = "Java Hardcore";
RecompileFinalReplacementusing the following command:
>ant -Dexample=oreilly/hcj/finalstory/FinalReplacement.java compile_example
compile_example:
[javac] Compiling 1 source file to C:devhcjbin
Now run theExternalUserexample again:
>ant -Dexample=oreilly.hcj.finalstory.ExternalUser run_example
run_example:
[java] The title of the book is: Hardcore Java.
There is no change despite the change in theA_STRINGconstant. To fix this problem, recompile theExternalUserclass:
>ant -Dexample=oreilly/hcj/finalstory/ExternalUser.java compile_example
compile_example:
[javac] Compiling 1 source file to C:devhcjbin
Running the example once more gives you the output you were seeking when you changedA_STRING inFinalReplacement:
>ant -Dexample=oreilly.hcj.finalstory.ExternalUser run_example
run_example:
[java] The title of the book is: Java Hardcore.
The Java compiler doesn’t automatically notice the dependency between theStringand 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.
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.
Next: Excessive Constants >>
More Java Articles
More By O'Reilly Media
|
This article is excerpted from chapter two of Hardcore Java, written by Robert Simmons Jr. (O'Reilly, 2004; ISBN: 0596005687). Check it out at your favorite bookstore. Buy this book now.
|
|