Option Constants

Option constants often bear a striking resemblance to bit field constants. However, the main difference between them is that a bit field uses powers of two for successive constants, while option constants are often numbered sequentially and use combinations that wouldn't make sense for a bit field. For example, consider this constant:

public final static int CONSTANT = 3;


If you see a constant such as this, the value of 3 is a dead giveaway that it isn't a bit field constant. Option constants also differ from substitution constants in that they represent a concept and are not merely there for efficiency or readability. If you consider a constant other than an option constant, the situation becomes a bit easier to understand:

public final static int MAX_COKES_PER_DAY = 200;


Clearly, this constant is there to hold a significant value. However, the constant itself doesn't signify anything in particular. Compare that with the following examples of option constants:

public final static String LOCALE_US = 0;
public final static String LOCALE_UK = 1;
public final static String LOCALE_DE = 2;


Here, the constant represents various available options. Your code may use constants such as these to represent the three locales used in your GUI. The most common kind of option constant is the integral option constant, which uses integral numbers to distinguish between option values, such as in the previous code snippet. However, you can use anything as the value of the option constant as long as there are no other constants for the same option that use the same value. The following code is valid even though integers are not used to distinguish option constants (although it is probably a little silly):

public final static int LOCALE_US = "fred";
public final static int LOCALE_UK = "joe";
public final static int LOCALE_DE = "john";


Option constants are concerned only with differentiation between the various constants, not with how this differentiation is accomplished. However, most option constants use integral values because these don't take as long to compare as string values. Option constants are used rather heavily in the JDK, as well as in other code. They are used for everything from text alignment in a GUI to exception error codes. However, option constants suffer from several design flaws.

Defects of Option Constants

Option constants suffer from a few critical problems that make using them a dangerous task. To illustrate, consider some constants used to represent countries, as shown in Example 7-6.

Example 7-6. Option constants for countries
package oracle.hcj.constants;
public class Country {
 public static final int CANADA = 0;
 public static final int CROATIA = 1;
 public static final int GERMANY = 2;
 public static final int ITALY = 3;
 public static final int MEXICO = 4;
 public static final int UK = 5;
 public static final int USA = 6;
}


This approach should be familiar. The problems with the approach result from the constants. Here is what happens if they are used:

package oracle.hcj.constants;
public class Address {
 private int country;
 public void setCountry(final int country) {
 this.country = country;
 }
 public int getCountry( ) {
 return this.country;
 }
}


Although this code looks normal, it suffers from a gaping hole. The user can pass the setCountry( ) method any integer she wants, whether it represents a valid country or not. To block this attempt, you need to alter your original Country class and add a couple more static members:

package oracle.hcj.constants;
public class Country {
 public static final int CANADA = 0;
 public static final int CROATIA = 1;
 public static final int GERMANY = 2;
 public static final int ITALY = 3;
 public static final int MEXICO = 4;
 public static final int UK = 5;
 public static final int USA = 6;
 public static final int MIN_VALUE = 0;
 public static final int MAX_VALUE = 6;
}


Now that you have the MIN_VALUE and MAX_VALUE constants and can change the setter to implement range checking, as shown here:

package oracle.hcj.constants;
public class Address {
 public void setCountry2(final int country) {
 if ((country < Country.MIN_VALUE) || (country > Country.MAX_VALUE)) {
 throw new IllegalArgumentException( );
 }
 this.country = country;
 }
}


simply check the incoming integer to make sure it is within the range of the bounded constants. If it isn't, then throw an IllegalArgumentException. Now the user has to give the setter a valid country. Unfortunately, throughout the program, you will have to provide the same validation. This checking code will be all over your server-side code, client-side code, and so on. It will be the responsibility of the user of the Country class to determine whether a value of this class is valid. This violates the principles of object-oriented engineering. If that doesn't bother you, it gets worse. If you want to add a new country to your list, you could make the following change:

package oracle.hcj.constants;
public class Country {
 public static final int CANADA = 0;
 public static final int CROATIA = 1;
 public static final int GERMANY = 2;
 public static final int ITALY = 3;
 public static final int MEXICO = 4;
 public static final int UK = 5;
 public static final int USA = 6;
 public static final int VENEZUELA = 7;
 public static final int MIN_VALUE = 0;
 public static final int MAX_VALUE = 6;
}


The code looks okay, but it has a nasty, lurking bug. In the example, the MAX_VALUE constant was not incremented to preserve your checking code. It's a good thing you saw this error now or you may have been in big trouble when the system deployed. The only time you would have encountered this bug would have been when a user tried to set a country value as VENEZUELA and was rejected by the checking code. Since a live system would be far more complex than this example, locating this tiny bug could take hours. There are a number of other ways to complicate things. If two countries had the same value, this would be a huge problem. Suppose you introduce the country of Uruguay and give it the value 1 by accident. Then if a user tries to set an address as being in the World, the system may interpret the value as Uruguay. The situation with integral option constants gets even worse when you consider the code in Example 7-7.

Example 7-7. Exception type constants
package oracle.hcj.constants;
public class NetAppException extends Exception {
 /** An undefined network exception. */
 public static final int NETWORK_UNDEFINED = 100;
 /** A network exception caused by the server dropping you. */
 public static final int NETWORK_SEVER_RESET = 101;
 /** A network exception caused by undefined hostname. */
 public static final int NETWORK_INVALID_HOST_NAME = 102;
 /** A bad parameter exception. */
 public static final int LOGIC_BAD_PARAMETER = 200;
 /** An exception caused by failed authorization. */
 public static final int LOGIC_AUTHORIZATION_FAILED = 201;
}


In this code, you declare a number of exception type codes to be used in generated exceptions. The exception type codes in Example 7-6 are grouped by type. All of the type codes for network errors are in the 100 to 199 range, and all of the ones for logic errors are in the 200 to 299 range. The organization of your constants in this manner helps you when you want to add new exception types to your system. Since you have room in between the values, you can easily insert them.

A Private Constructor?

In , I warned you about using private constructors. However, the situation in Example 7-7 is one time when using them is acceptable. In this case, the class declares only static members. Inheriting this class into another would be pointless because the user of the subclass would still have to refer to the static members by the name of the base class. For example, if your subclass was SomeExceptionType, referencing SomeExceptionType.LOGIC_BAD_PARAMETER would cause a compiler error. However, note that in addition to the private constructor being created, the class was declared as final. Always be explicit. If there is a reason to make a class final, come right out and say it.


But now your entire MIN_VALUE and MAX_VALUE validation solution is trash. To fix this, you could build an array for validation or a set of valid constants. To implement this, you will have to change all of the locations in the program where these constants are used—a very unpleasant and boring job. Even after doing all of that work, the object-oriented problem remains unsolved: the users of this class are still responsible for validation. If the junior programmer working on your GUI is sleepy one day and forgets these rules, you could have a horrible hidden bug in your system. Clearly, you need an alternative to this archaic type of constant declaration. That is where constant objects come into the picture.

      
Comments