Stranger Things in Java: Constants
Let's see how constants work in some strange scenarios.
Join the DZone community and get the full member experience.
Join For FreeWhat you are reading is the fourth in a series of articles titled "Stranger things in Java", inspired by the contents of my book "Java for Aliens". These articles are dedicated to insights of the Java language. Deepening the topics we use every day will allow us to master the Java coding even in the strangest scenario.
Introduction
In this article, we will explore some scenarios involving the use of constants where even experienced programmers may have doubts. Although the topic may be well known, not everyone has explored particular scenarios such as solving multiple inheritance in presence of homonymous constants. Strengthening one's theoretical basis is essential to be able to program with confidence.
Definition
A constant is a variable whose value cannot change once it has been assigned. This means that if an instruction in our program tries to change the value of the constant, we will get a compile-time error. What transforms a variable into a constant in Java, is the final
modifier. It limits the number of possible assignments to a variable to one. For example, if we write:
final int CONST = 0;
CONST = 1;
we get the following compilation error:
xxxxxxxxxx
error: cannot assign a value to final variable CONST
CONST = 1;
^
1 error
This is why the naming convention of the constants requires that you only use uppercase letters (and the underscore character "_
" in case of names consisting of several words). This particular convention in fact, guarantees us to be able to immediately distinguish a constant from a variable. This will make it more difficult to make a mistake like the previous one.
Constant Categorization
As we are used to categorizing variables into instance variables, class variables and local variables, we must also distinguish class constants, instance constants and local constants. In particular, in the previous snippet we defined a local constant.
Local constants are to be considered, even some special cases such as that of the constant parameters of the methods (see dedicated section below), or the constants defined contextually to the declaration of some programming constructs. For example, we could write a foreach loop by declaring the temporary variable as a constant as follows:
xxxxxxxxxx
for (final var tmp : arr) {
System.out.println(tmp);
}
Some developers find this practice useful to emphasize that the temporary variable must not be changed within the construct's code block. Note that, being able to declare the variable tmp
as a constant, makes us understand that this variable is actually local not with respect to the loop construct, but with respect to the current iteration of the loop. If this were not the case, the final
modifier would prevent the reassignment of a value (thus the previous loop could not be compiled).
This behavior is different than that of the variable we usually initialize in a for
loop, which has a loop-level scope and not an iteration-level scope. In fact, the following snippet:
xxxxxxxxxx
for (final int i = 0; i < 10; i++) {
//code omitted
}
will produce the error:
xxxxxxxxxx
error: cannot assign a value to final variable i
for (final int i = 0; i < 10; i++) {
^
1 error
After this observation, let's move on to see some more interesting scenarios.
Local Constants
Local constants are relatively little used today, but this was not always the case. In fact, there is also a programming style that requires the use of the final
modifier for each local variable to which a single value is assigned during its life cycle. In fact, by declaring final
a variable whose value will not be modified, it immediately makes clear the logic with which it was defined: its value must not change. It is a way to put a constraint in our code that must also be respected by other programmers who will modify it in the future. This style of programming is consistent with the principles of object-oriented design and is considered by some to be a best practice. For example, when with some IDEs like Eclipse you perform an “extract local variable” refactoring to assign the return value of a method to an automatically created variable, by default the final
modifier is added to that variable as well.
Figure 1: The Eclipse dialog that adds final when "extract a local variable"
However, Java is known to be as expressive as it is verbose. For this reason, the tendency to add the final
modifier to a variable that does not change its value is relatively little used today. Oracle itself, which is working so hard to reduce the proverbial verbosity of Java, by introducing the concept of effectively final variable in version 8, has also given a clear indication: programmers prefer a less verbose style. In fact, before Java 8 to use a local variable within a local nested class (and subsequently in lambda expressions) it was mandatory to declare it, while now it is enough for the variable to be initialized only once.
Constant Parameters
The parameters of a method can also be declared final
. For example, we can declare the main
method parameter final
to prevent reassignment. For example:
xxxxxxxxxx
public static void main(final String args[]) {
args = new String[5];
//rest of code omitted...
will produce the following compile-time error:
xxxxxxxxxx
error: final parameter args may not be assigned
args = new String[5];
^
1 error
Even in the case of method parameters, the use of final
is usually avoided, but in some situations it can be useful. The reason is always the same, to make the code more expressive while increasing the verbosity.
Instance Constants
But most of the time we use constants as fields of our classes. In particular, we usually initialize a constant contextually to its declaration, for example in the following way:
xxxxxxxxxx
public class FileManager {
private final static String FILE_NAME = "aFile.java";
//rest of code ommitted...
}
Note that in this case, we have declared the constant also static
, and this can be considered a best practice. In this way, in fact, the static
modifier will ensure that there is a single copy of the constant for that class, which will be shared by all the instantiated objects. This will prevent identical copies of the constant from being created in memory at runtime for each instantiated object. In fact, a constant has a fixed value, so it is not risky that all objects of the same class share it since its value cannot change. The constants declared final
and static
are called class constants.
Obviously, if we want each instantiated object to have a different value for its constant, then the static
modifier should not be used.
Instance Constants
We said that usually we are used to initialize a constant at the same time as its declaration. But this is not mandatory, indeed it is also possible to initialize an instance constant within a constructor. The following class for example:ù
xxxxxxxxxx
public class FileManager {
private final String FILE_NAME;
public FileManager(String fileName) {
FILE_NAME = fileName;
}
}
has a constructor that assigns the value to the FILE_NAME
constant through a parameter that comes from outside. This allows you to assign a different value to the constant FILE_NAME
for each object. For example, we can write:
xxxxxxxxxx
FileManager readmeFM = new FileManager("readme.txt");
FileManager licenseFM = new FileManager("license.txt");
In this way, the two objects will have the constant FILE_NAME
initialized differently.
Obviously, if we had declared the constant FILE_NAME
also static
, this would not have been possible. Beware that in that case, the compiler would have given a misleading error message:
xxxxxxxxxx
error: cannot assign a value to final variable FILE_NAME
FILE_NAME = fileName;
^
1 error
In fact, it seems to indicate that the problem is the final
modifier, but in reality the problem derives from the use of the static
modifier.
Instance Constants and Methods
Note that it is not possible to set the value of an instance constant within a method that is not a constructor. For example, if we wrote:
xxxxxxxxxx
public class FileManager {
private final String FILE_NAME;
public FileManager(String fileName) {
setFILE_NAME(fileName);
}
public void setFILE_NAME(String fileName) {
FILE_NAME = fileName;
}
}
we would get the following compilation error:
xxxxxxxxxx
error: cannot assign a value to final variable FILE_NAME
FILE_NAME = fileName;
^
1 error
This is because, unlike a constructor, the setFILE_NAME
method could be called at runtime more than once, causing the violation of the contract defined by the final
modifier.
Instance Constants and Constructor Overloads
Also note that in the case of constructor overloads, the compiler will be able to recognize if the constant is initialized correctly. For example, if we added an instructionless constructor to the FileManager
class as follows:
xxxxxxxxxx
public class FileManager {
private final String FILE_NAME;
public FileManager() {
}
public FileManager(String fileName) {
FILE_NAME = fileName;
}
}
the compiler will understand that if we call the second constructor, the constant FILE_NAME
will not be initialized and therefore will present us with this error message:
xxxxxxxxxx
error: variable FILE_NAME might not have been initialized
}
^
1 error
If, on the other hand, with the first constructor we call the second constructor using the this
keyword, passing it the name of a default file, then the program will compile correctly:
xxxxxxxxxx
public class FileManager {
private final String FILE_NAME;
public FileManager() {
this("defaultFile.txt");
}
public FileManager(String fileName) {
FILE_NAME = fileName;
}
}
In fact, there will be no possibility to set FILE_NAME
multiple times.
Constants and Encapsulation
Likely, our instance or class constants are also declared public
. In fact, even if we are used to encapsulating our variables to prevent them from assuming unwanted values, it makes no sense to encapsulate our constants, as they can never assume an unwanted value.
In the standard Java library, we can find many static
and public
constants, such as the PI
and E
constants of the Math
class, or MAX_PRIORITY
, NORM_PRIORITY
and MIN_PRIORITY
of the Thread
class.
Constants, Polymorphism, and Inheritance
For the rules of polymorphism, we know that if we invoke a method of an object using a reference of a superclass, the rewritten method of the class with which the object was instantiated will be invoked, and not the method of the reference class we are using. In fact, if we consider this simple class hierarchy:
xxxxxxxxxx
abstract class Pet {
public final String type = "Generic Pet";
public abstract void talk();
}
class Dog extends Pet {
public final String type = "Dog";
public void talk() {
System.out.println("Woof woof!")
}
}
with the following snippet:
xxxxxxxxxx
Pet bobby = new Dog();
bobby.talk();
we will invoke the talk
method redefined in the Dog
class and not the original method of the Pet
class (which by the way was abstract).
The situation changes if we try to access public variables or constants (both static
and non-static
), which we overwrite in the subclasses. In fact, there is no override for the attributes of a class, and therefore the rules are different. For example, the following code:
xxxxxxxxxx
Pet snoopy = new Dog();
System.out.println(snoopy.name);
Dog punky = new Dog();
System.out.println(punky.name);
will result in the following output:
xxxxxxxxxx
Generic Pet
Dog
which implies that even if the constant name has been rewritten in the Dog
subclass, to access it we need a reference of the same class. In fact, using the reference of the Pet
superclass, the constant of the Pet
class is accessed.
Constants and Multiple Inheritance
From version 8 of Java, with the introduction of default methods in the interfaces, it is possible to use a new kind of multiple inheritance. This is not the same complex feature defined in some languages such as C ++, but a simple consequence of the evolution of the interface concept. The rules governing Java's multiple inheritance are very simple, and the only one that can raise some doubts is known as "class always wins". In practice, if we inherit two methods with the same signature from a class and an interface, the one of the class will always be inherited (the class always wins). In all other cases of homonymy of inherited methods, the compiler forces us to override the method.
That said, we know that interfaces can't declare variables, but they can declare static and public constants. In fact, there is not even the obligation to mark them with the public
, static
and final
modifiers, which are implicit in the interfaces. So, how does it work if we inherit a constant with the same name from two different types? The answer is that the compiler will always force us to rewrite the constant. For example. let's consider the following code:
xxxxxxxxxx
abstract class AbstractClass {
public static final int VALUE = 1;
}
interface Interface {
int VALUE = 2;
}
class Subclass extends AbstractClass implements Interface {
public static void main(String args[]) {
System.out.println(VALUE);
}
}
If we tried to compile the file containing the previous code, we would get the following compilation error:
xxxxxxxxxx
error: reference to VALUE is ambiguous
System.out.println(VALUE);
^
both variable VALUE in AbstractClass and variable VALUE in Interface match
1 error
So, for the constants, the “class always wins” rule does not apply. In particular, in the act of rewriting the constant, the class to which it belongs must always be referenced, for example:
System.out.println(AbstractClass.VALUE);
Conclusion
In this article, we have explored some aspects of the use of constants, a basic topic of the language that is sometimes used with superficiality. Instead, we have seen that there are situations where the constants have a singular behavior. As usual, having a solid theoretical basis will allow us to manage all situations without surprises.
Author’s Notes
This article is based on a few paragraphs from my English book "Java for Aliens".
Published at DZone with permission of Claudio De Sio Cesari. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments