Tuesday, August 3, 2021

How public static final variable works in Java? Example

Hello guys, how does the public static final variable works in Java is it a common Java interview question on interviews? It is also asked as to what is the difference between a public static final variable and a public final (non-static) variable in Java? Does both are same? It is one of the simple, yet tricky questions that may Interviewers like to ask candidates, and would you believe that almost 50% of Java developers miss the key point to mention here, which we'll see in this article. In short, No, they are not the same. Even though both are final variables and you cannot change their value once assigned there is a very subtle difference between them.

A public static final variable is a compile-time constant, but a public final is just a final variable, i.e. you cannot reassign value to it but it's not a compile-time constant. This may look puzzling, but the actual difference allows how the compiler treats those two variables.

For public static final variables, values are copied at client end at compile-time, which means if the value is coming from a third party JAR and is changed in the subsequent release but the client has not compiled again, maybe because they are indifferent JAR file then the client will continue be using the old value.

This is actually one of the mysterious gotchas of the Java programming language and creates really hard-to-find bugs.


How public static final variable works in Java?


Let's say you have a public static final variable to validate the length of the username in your application. The variable username is a compile-time constant in the AppUtils class, as shown below:

public class AppUtils {

  public static AppUtils _INSTANCE = new AppUtils();
  public static final int USERNAME_LENGTH = 8;
  
  private AppUtils(){
    // no instance allowed outside this class
  }
  
  public static AppUtils getInstance(){
    return _INSTANCE;
  }
  
}


The AppUtils class is in a separate JAR file, the utils.jar and you created a client which depends upon this value to validate username and password, but the client and this constant are in different JAR files i.e. app.jar and utils.jar

import java.util.Scanner;

public class App {

  public static void main(String args[]) {

    // read username and password and validate length
    // using AppUtil constants
    Scanner cmdReader = new Scanner(System.in);

    System.out.println("Please enter username");
    String username = cmdReader.nextLine();

    if (username.length() > AppUtils.USERNAME_LENGTH) {
      System.out.println("Please enter a shorter username, max characters :"
          + AppUtils.USERNAME_LENGTH);
    }
    
    cmdReader.close();
  }
}


Now, let's run our program from the command line by including the dependent JAR file into classpath as shown here:

How public static final variable works in Java?

You can see that the program is behaving as expected. Later we change the value of the final variable to 20 and replace the library JAR, the utils.jar in our case, with a new version, but we forgot to compile our client application i.e. the App class, which validates the length of username and password. Now, let's see what happens:

public static final variable example in Java


Oops, it's still using the same old value and complaining about username is greater than the length of 8, even though we have increased the length to 20 and put a new JAR file. Since value is inlined even after updating the dependent JAR file it's not reflected in your program because we have not compiled the App.java class again.

In order to solve this problem, I compiled the App.jar again against the new version of utils.jar and then ran the program again, you can see in the last line that it didn't complain now because it's using an updated value, which allows username up to 20 characters. This issue can be really tricky to find out sometimes. The Java Puzzlers book from Joshua Bloch also has some puzzles to educate Java developers about this point. You may want to check that as well.

public static final variable works in Java


Important points

In short, here are some key differences between a public static final variable and normal final variables in Java:

1) The public static final variable is usually a compile-time constant if the value is known to the compiler at compile time e.g.
public static final int USERNAME_LENGTH = 8;
but it may not be a compile-time constant if you use expressions as shown below:
public static final int USERNAME_LENGTH = getConstant();
in this case, USERNAME_LENGTH is not a compile-time constant.


2) If a public static final variable is a compile-time constant then its value is also inlined at the client end, hence it can remove runtime dependency on a third party library at the bytecode level.

3) Because of the above reason, it's also advised to compile your project every time you update the third-party libraries in your project. If the value of a compile-time constant is updated in third party JAR it won't be reflected in your project until you compile it again because values are copied at compile time. You can read Java Puzzlers to learn more about that.


4) When you create a JAR file from Eclipse, make sure you include the name of the main class for creating executable JAR files, otherwise, you won't be able to run them from the command prompt. In our example, the utils.jar was a library without any main class but app.jar was an executable JAR file, contains the Main-Class attribute in the manifest file, that's why we could run it using the java -jar option. See here to learn more about how to create an executable JAR file using Eclipse.


That's all about the difference between the public static final and public final variable in Java. Remember, the former is a compile-time constant and their value is inlined at compile time at the client end, which means, if the value is changed you need to compile all clients explicitly to pick the new value, otherwise they will keep using the old value. This is one reason, Why you should compile your whole project when you upgrade to a newer version of a library. Contrary to that, public final variables are not inlined.

2 comments:

  1. private AppUtils(){
    // no instance allowed outside this class
    }
    can we make a constructor private

    ReplyDelete