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 Free
What 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.
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.
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:
we get the following compilation 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.
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:
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:
will produce the error:
After this observation, let's move on to see some more interesting scenarios.
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.
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:
will produce the following compile-time 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.
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:
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
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.
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:ù
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:
In this way, the two objects will have the constant
FILE_NAME initialized differently.
Obviously, if we had declared the constant
static, this would not have been possible. Beware that in that case, the compiler would have given a misleading error message:
In fact, it seems to indicate that the problem is the
final modifier, but in reality the problem derives from the use of the
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:
we would get the following compilation 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
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:
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:
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:
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
public constants, such as the
E constants of the
Math class, or
MIN_PRIORITY of the
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:
with the following snippet:
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:
will result in the following output:
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
Petsuperclass, 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
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:
If we tried to compile the file containing the previous code, we would get the following compilation 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:
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.
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.