You are currently viewing Stranger things in Java: Constants

Stranger things in Java: Constants

What you are reading is one 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.

 

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:

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:

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 loop-level scope and not an iteration-level scope. In fact, the following snippet:

for (final int i = 0; i < 10; i++) {
//…
}

will produce the error:

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 final 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:

public static void main(final String args[]) {
    args = new String[5];
//. . .

will produce the following compile-time error:

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.

 

Class 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:

public class FileManager {
    private final static String FILE_NAME = "aFile.java"; //. . . }

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

Pet snoopy = new Dog();
System.out.println(snoopy.name);
Dog punky = new Dog();
System.out.println(punky.name);        

will result in the following output:

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:

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:

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“.

 

Leave a Reply