13.4.8 final Fields and Constants

If a field that was not final is changed to be final, then it can break compatibility with pre-existing binaries that attempt to assign new values to the field. For example, if the program:

class Super { static char s; }


class Test extends Super {
	public static void main(String[] args) {
		s = 'a';
		System.out.println(s);
	}
}

is compiled and executed, it produces the output:

a

Suppose that a new version of class Super is produced:

class Super { static char s; }

If Super is recompiled but not Test, then running the new binary with the existing binary of Test results in a IncompatibleClassChangeError. (In certain early implementations of Java this example would run without error, because of a flaw in the implementation.)

We call a field that is static, final, and initialized with a compile-time constant expression a primitive constant. Note that all fields in interfaces are implicitly static and final, and they are often, but not always, constants.

If a field is not a primitive constant, then deleting the keyword final or changing the value to which the field is initialized does not break compatibility with existing binaries.

If a field is a primitive constant, then deleting the keyword final or changing its value will not break compatibility with pre-existing binaries by causing them not to run, but they will not see any new value for the constant unless they are recompiled. If the example:

class Flags { final static boolean debug = true; }


class Test {
	public static void main(String[] args) {
		if (Flags.debug)
			System.out.println("debug is true");
	}
}

is compiled and executed, it produces the output:

debug is true

Suppose that a new version of class Flags is produced:

class Flags { final static boolean debug = false; }

If Flags is recompiled but not Test, then running the new binary with the existing binary of Test produces the output:

debug is true

because the value of debug was a compile-time primitive constant, and could have been used in compiling Test without making a reference to the class Flags.

This behavior would not change if Flags were changed to be an interface, as in the modified example:

interface Flags { boolean debug = true; }
class Test {
	public static void main(String[] args) {
		if (Flags.debug)
			System.out.println("debug is true");
	}
}

(One reason for requiring inlining of primitive constants is that Java switch statements require constants on each case, and no two such constant values may be the same. Java checks for duplicate constant values in a switch statement at compile time; the class file format does not do symbolic linkage of case values.)

The best way to avoid problems with "inconstant constants" in widely-distributed code is to declare as primitive constants only values which truly are unlikely ever to change. Many primitive constants in interfaces are small integer values replacing enumerated types, which Java does not support; these small values can be chosen arbitrarily, and should not need to be changed. Other than for true mathematical constants, we recommend that Java code make very sparing use of class variables that are declared static and final. If the read-only nature of final is required, a better choice is to declare a private static variable and a suitable accessor method to get its value. Thus we recommend:

private static int N;
public static int getN() { return N; }

rather than:

public static final int N = ...;

There is no problem with:

public static int N = ...;

if N need not be read-only. We also recommend, as a general rule, that only truly constant values be declared in interfaces. We note, but do not recommend, that if a field of primitive type of an interface may change, its value may be expressed idiomatically as in:


interface Flags {
	boolean debug = new Boolean(true).booleanValue();
}

insuring that this value is not a constant. Similar idioms exist for the other primitive types.

One other thing to note is that static final fields that have constant values (whether of primitive or String type) must never appear to have the default initial value for their type (§4.5.4). This means that all such fields appear to be initialized first during class initialization (§8.3.2.1, §9.3.1, §12.4.2).