We need to distinguish two aspects of constants:
- names for a values known at development time, which we introduce for better maintainability, and
- values that are available to the compiler.
And then there's a related third kind: variables whose value does not change, i.e. names for a value. The difference between an these immutable variables and a constant is when the value is determined/assigned/initialized: a variable is initialized at runtime, but the value of a constant is known during development. This distinction is a bit muddy since a value may be known during development but is actually only created during initialization.
But if the value of a constant is known at compile-time, then the compiler can perform computations with that value. For example, the Java language has the concept of constant expressions. A constant expression is any expression that consists only of literals of primitives or strings, operations on constant expressions (such as casting, addition, string concatenation), and of constant variables. [JLS §15.28] A constant variable is a final
variable that is initialized with a constant expression. [JLS §4.12.4] So for Java, this is a compile-time constant:
public static final int X = 7;
This becomes interesting when a constant variable is used in multiple compilation units, and then the declaration is changed. Consider:
Now when we compile these files the B.class
bytecode will declare a field Y = 9
because B.Y
is a constant variable.
But when we change the A.X
variable to a different value (say, X = 0
) and recompile only the A.java
file, then B.Y
still refers to the old value. This state A.X = 0, B.Y = 9
is inconsistent with the declarations in the source code. Happy debugging!
This doesn't mean that constants should never be changed. Constants are definitively better than magic numbers that appear without explanation in the source code. However, the value of public constants is part of your public API. This isn't specific to Java, but also occurs in C++ and other languages that feature separate compilation units. If you change these values, you will need to recompile all dependent code, i.e. perform a clean compile.
Depending on the nature of the constants, they might have led to incorrect assumptions by the developers. If these values are changed, they might trigger a bug. For example, a set of constants might be chosen so that they form certain bit patterns, e.g. public static final int R = 4, W = 2, X = 1
. If these are changed to form a different structure like R = 0, W = 1, X = 2
then existing code such as boolean canRead = perms & R
becomes incorrect. And just think of the fun that would ensue were Integer.MAX_VALUE
to change! There is no fix here, it's just important to remember that the value of some constants really is important and cannot be changed simply.
But for the majority of constants changing them is going to be fine as long as the above restrictions are considered. A constant is safe to change when the meaning, not the specific value is important. This is e.g. the case for tunables such as BORDER_WIDTH = 2
or TIMEOUT = 60; // seconds
or templates such as API_ENDPOINT = "https://api.example.com/v2/"
– though arguably some or all of those ought to be specified in configuration files rather than code.
final
gives you a compiler-enforced guarantee that the program won't modify the value. I wouldn't dispense with that just because the programmer might want to modify the value assigned in the source code.gravity
mid-game/run. They don't necessarily mean,gravity
is the same on every planet ... That said, the healthy solution is to makegravity
a constant, but pull it from aplanet
file or database at the start of the relevant scope.