DEV Community

Dapinder
Dapinder

Posted on

Are you aware of this usage of final keyword in java?

Most of us are aware of final keyword in java for the following:

  1. Prevent Inheritance by declaring class as final.
  2. Prevent method overriding by making method as final.
  3. Declaring constants.

I am here to let know more advantages of final keyword which are not so generally aware of:

Primitive types like int, char, float etc. other than arrays are substituted at compile time with their values, if you change a final that is used by other classes, you must remember to recompile those other classes or your change will not take effect. The same rule applies to constants of type java.lang.String. Although String is a constructed type, it is also substituted at compile time. All constructed types other than String, mutable or not, are not substituted at compile time. To understand how this works

Consider the following code fragment:

package com.dapinder.practice;

public class FinalConstantSubstitute {

 /** A string constant */
 public final static String STRING_VAR = "Dapinder's Blog";

 /** An int constant. */
 public final static int INT_VAR = 15;

 /** A double constant. */
 public final static double DOUBLE_VAR = 13.23d;

 /** An array constant. */
 public final static int[] ARRAY_VAR = new int[] { 2, 2, 2, 2, 2, 2 };

 /** A Wrapper constant. */
 public final static Integer A_OTHER_WRAPPER = new Integer(10);

 public static void main(String[] args) {

  System.out.println(STRING_VAR);
  System.out.println(INT_VAR);
  System.out.println(DOUBLE_VAR);
  System.out.println(ARRAY_VAR);
  System.out.println(A_OTHER_WRAPPER);

 }
}
Enter fullscreen mode Exit fullscreen mode

Once the compiler sees a code as above, it optimises it and starts substituting the primitives and String objects.

ByteCode view of the FinalConstantSubstitute post as follows: (used java decompiler)

ByteCode view

Be cautious of using final constants when accessing in some other classes. Since above is a single class so its not getting noticed but if you use the declared public constants in some other class, then changing of constant, you have to recompile the class making call.

Check below for example:

 package com.dapinder.practice;

 public class FinalConstantSubstituteMain {

 /**
 * The class using declared final constants.
 * 
 * @param args
 */
 public static void main(String[] args) {

  /** Substituted values */
  System.out.println(FinalConstantSubstitute.STRING_VAR);
  System.out.println(FinalConstantSubstitute.INT_VAR);
  System.out.println(FinalConstantSubstitute.DOUBLE_VAR);
  System.out.println(FinalConstantSubstitute.ARRAY_VAR);
  System.out.println(FinalConstantSubstitute.A_OTHER_WRAPPER);

 }
}
Enter fullscreen mode Exit fullscreen mode

ByteCode view

ByteCode view

Here compiler will substitute the constant's values from FinalConstantSubstitute. For example, change of constant STRING_VAR to some other value say Google will not reflect in FinalConstantSubstituteMain unless FinalConstantSubstituteMain is re-compiled. The reason is obvious since substitution happens at compile time and is done by the compiler.

Conditional Compilation

This will amaze you when you think how final variables can provide a way where certain lines get conditional compilation based on a particular condition. This can actually save on most debugging code in production and will improve the performance as well.

To understand its power, lets take an example:

package com.dapinder.practice;

public class ConditionalFinal {

 private static boolean isLoggingEnabled = false;

 /**
 * @param args
 */
 public static void main(String[] args) {
  if (isLoggingEnabled) {
   System.out.println("Statment 1");
  }
  if (isLoggingEnabled) {
   System.out.println("Statment 2");
  }
  if (isLoggingEnabled) {
   System.out.println("Statment 3");
  }
  if (isLoggingEnabled) {
   System.out.println("Statment 4");
  }
 }
}
Enter fullscreen mode Exit fullscreen mode

Please note that variable isLoggingEnabled is not declared final here.

Bytecode view:

Bytecode

Please note that all if conditions are very well referencing the variable and if the isLoggingEnabled is true, i will print on the console and when false it wont print but yet have although less on performance as it will check each conditional IF statement.

This is fine and at first glance does not seem expensive but in production where one method is called by millions of threads and multiple times, this can get expensive.

Most of us know and have used Log4J and we know not all logging statements are turned on in production. Most of us even do a isDebugEnabled before logging a debug statement.
However When using the isDebugEnabled( ), the cost is much more than one comparison per if statement.

Now check by converting the isLoggingEnabled variable into a final variable:

 package com.dapinder.practice;

public class ConditionalFinal {

 private static final boolean isLoggingEnabled = false;

 /**
 * @param args
 */
 public static void main(String[] args) {
  if (isLoggingEnabled) {
   System.out.println("Statment 1");
  }
  if (isLoggingEnabled) {
   System.out.println("Statment 2");
  }
  if (isLoggingEnabled) {
   System.out.println("Statment 3");
  }
  if (isLoggingEnabled) {
   System.out.println("Statment 4");
  }
 }
}
Enter fullscreen mode Exit fullscreen mode

By converting the isLoggingEnabled attribute into a final attribute, you have told the compiler that whenever it sees isLoggingEnabled, it should replace it with false as per substitution principle explained above.

One more important point here.

Most of us do get WARNING in any IDE (say Eclipse) whenever a piece of code is unreachable. Here compiler applies the same principle and removes all those statement which are meant to be unreachable or will never execute.

Here, in above example, all these logging statements will disappear.

Bytecode

Top comments (0)