Java has this keyword — final. It can be applied to classes, methods, variables (including method parameters). For a class, the final keyword means that the class cannot have subclasses, i.e. inheritance is forbidden... This is useful when creating immutable (unchangeable) objects. For example, the String class is declared as final.

    public final class String {
    }
    
    class SubString extends String { // Compilation error
    }
I should also note that the final modifier cannot be applied to abstract classes (those with the keyword abstract), because these are mutually exclusive concepts. For a final method, the modifier means that the method cannot be overridden in subclasses. This is useful when we want to prevent the alteration of the original implementation.

    public class SuperClass {
        public final void printReport() {
            System.out.println("Report");
        }
    }

    class SubClass extends SuperClass { 
        public void printReport() { //Compilation error
            System.out.println("MyReport");
        }
    }

Java Final Variable

For variables of a primitive type, the final keyword means that the value, once assigned, cannot be changed. For reference variables, it means that after an object is assigned, you cannot change the reference to that object. This is important! The reference cannot be changed, but the object's state can be changed. Java 8 introduced a new concept: effectively final. It applies only to variables (including method parameters). The bottom line is that despite the clear absence of the final keyword, the variable's value does not change after initialization. In other words, the final keyword can be applied to such a variable without a compilation error. Effectively final variables can be used inside local classes (local inner classes), anonymous classes (anonymous inner classes), and streams (Stream API).

        public void someMethod() {
            // In the example below, both a and b are effectively final, since they are assigned values only once:
            int a = 1;
            int b;
            if (a == 2) b = 3;
            else b = 4;
            // c is NOT effectively final since its value changes
            int c = 10;
            c++;
            
            Stream.of(1, 2).forEach(s-> System.out.println(s + a)); // OK
            Stream.of(1, 2).forEach(s-> System.out.println(s + c)); // Compilation error
        }
Now, let's have a little interview. After all, the goal of completing the CodeGym course is to become a Java developer and find an interesting and well-paid job. So, let's begin.
  1. What can we say about an array that is declared final?

  2. We know that the String class is immutable: the class is declared final. A string value is stored in a char array that is marked with the keyword final.


public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

Can we replace a String object's value (without changing the reference to the object)? These are real interview questions. And practice shows that many candidates do not answer them correctly. Understanding how the final keyword is used, especially for reference variables, is very important. As you contemplate this, I'll make a small request to the CodeGym team. Please give the text editor the ability to add a block whose content can be shown/hidden when you click on it. Answers:
  1. An array is an object, so the final keyword means that once a reference to the array is assigned, the reference cannot be changed. That said, you can change the state of the object.

    
            final int[] array = {1, 2, 3, 4, 5};
            array[0] = 9;	 // OK, because we're changing the contents of the array: {9, 2, 3, 4, 5}
            array = new int[5]; // Compilation error
    
  2. Yes, we can. The main thing is to understand what the thorny final keyword means when used with objects. The Reflection API can be used to replace values.


import java.lang.reflect.Field;

class B {
    public static void main(String[] args) throws Exception {
        String value = "Old value";
        System.out.println(value);

        // Get the String class's value field
        Field field = value.getClass().getDeclaredField("value");
        // Make it mutable
        field.setAccessible(true);
        // Set a new value
        field.set(value, "CodeGym".toCharArray());

        System.out.println(value);

        /* Output:
         * Old value
         * CodeGym
         */
    }
}
Please note that if we had tried to change the final variable of a primitive type in this way, then nothing would have happened. I suggest that you convince yourself: create a Java class, for example, with a final int field and try to change its value using the Reflection API.

Boosting Performance with final

Did you know that using final can actually make your programs faster? Yep! When you mark a variable, method, or class as final, the Java compiler and the Just-In-Time (JIT) compiler can make smarter decisions.

  • Method Inlining: If a method is final, the JIT compiler knows it won’t be overridden. This allows it to replace method calls with the actual code, speeding things up!
  • Better Optimizations: Immutable variables help the compiler skip redundant checks and improve memory handling.

Pretty cool, right? More speed without lifting a finger!

Enhancing Security with final

Let’s talk security. Ever worry about sneaky code changing your variables or overriding critical methods? final has your back!

  • Immutable Data: Declaring sensitive data as final prevents it from being reassigned. No funny business allowed!
  • Safe Inheritance: Mark critical classes or methods as final to block subclasses from altering behavior. No one can hijack your logic!

By locking things down, final adds a layer of defense against malicious attacks. Feels safer already!

Proper Initialization of final Variables

Okay, so final means no changes after assignment, but when and how do you assign it?

  • Immediate Assignment: final int age = 30; Easy peasy.
  • Constructor Assignment: For non-static final variables, you can set them in every constructor. No constructor should leave it unassigned!
    class Person {
      final String name;
      
      Person(String name) {
        this.name = name;
      }
    }
  • Static Initializer: Static final variables can be initialized in a static block.
    static final int ID;
    static {
      ID = 1001;
    }

As long as it’s assigned once, you're good to go!

The Non-Transitivity of final References

Ah, here's a subtle one! Declaring a reference as final means you can't reassign it, but the object it points to can still change. Confused? Let’s break it down:

final List fruits = new ArrayList<>();
fruits.add("Apple"); // Allowed
fruits = new ArrayList<>(); // Error!

See? The fruits list itself is locked in place, but its content can be modified. This is called non-transitivity. Want a truly immutable object? Use immutable classes like String or Collections.unmodifiableList().

Good luck to all!