Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A final variable in Java is a variable whose value cannot be reassigned once it has been explicitly initialized. Applying the final modifier to a primitive variable makes its value immutable, while applying it to an object reference prevents the reference from pointing to a different memory location, though the internal state of the referenced object may still be modified.

Initialization Rules

The Java compiler enforces strict definite assignment rules for final variables. The exact initialization requirements depend on the variable’s scope. 1. Local Variables A local final variable can be assigned at most once within its scope. It can be declared without initialization (a “blank final variable”) and assigned zero times if it is never read. However, it must be definitely assigned before any read operation. Any attempt to assign it more than once results in a compilation error, regardless of whether the variable is ever accessed.
public void calculate() {
    final int unused; // Valid: Assigned zero times, never read
    
    final int threshold;
    threshold = 10; // Valid: First and only assignment
    // threshold = 20; // Compilation Error: variable threshold might already have been assigned
    
    final int limit;
    // System.out.println(limit); // Compilation Error: variable limit might not have been initialized
}
2. Instance Variables A final instance variable must be definitely assigned exactly once by the end of object initialization, regardless of whether it is ever read. Failing to initialize a final field results in a compilation error even if the field is completely unused. It must be initialized through one of three mechanisms:
  • At the point of declaration.
  • Within an instance initialization block.
  • By the end of every constructor. If constructor chaining is involved (e.g., one constructor delegates to another using this(...)), the assignment must occur only in the delegated constructor. Attempting to assign it again in the delegating constructor results in a compilation error.
public class Configuration {
    final int maxConnections = 100; // Initialized at declaration
    final boolean isDebug;          // Blank final instance variable
    final String environment;       // Blank final instance variable

    // Initialized in an instance block
    {
        isDebug = false;
    }

    // Initialized in the constructor
    public Configuration(String env) {
        this.environment = env;
    }

    // Constructor chaining: delegates to the constructor above
    public Configuration() {
        this("production");
        // this.environment = "dev"; // Compilation Error: variable might already have been assigned
    }
}
3. Static Variables A final static variable (class-level constant) must be definitely assigned exactly once by the end of class initialization. It must be initialized through one of two mechanisms:
  • At the point of declaration.
  • Within a static initialization block.
public class Constants {
    public static final double PI = 3.14159; // Initialized at declaration
    public static final String OS_NAME;      // Blank final static variable

    // Initialized in a static block
    static {
        OS_NAME = System.getProperty("os.name");
    }
}

Reference Variables and Mutability

When the final keyword is applied to an object reference, it enforces reference immutability, not object state immutability. The variable cannot be reassigned to a new object, but the fields or elements of the referenced object can still be mutated if the object’s class permits it.
final StringBuilder builder = new StringBuilder("Hello");

// Valid: Mutating the internal state of the referenced object
builder.append(" World"); 

// Compilation Error: Cannot assign a new reference to a final variable
// builder = new StringBuilder("New String"); 

final int[] numbers = {1, 2, 3};

// Valid: Mutating the array elements
numbers[0] = 99; 

// Compilation Error: Cannot reassign the array reference
// numbers = new int[]{4, 5, 6}; 

Effectively Final Variables

Introduced in Java 8, a local variable or parameter is considered “effectively final” if it is not explicitly declared final but is never reassigned after its initial assignment. The Java compiler requires local variables captured by lambda expressions or anonymous inner classes to be either explicitly final or effectively final. This enforces variable immutability across different execution scopes without requiring the explicit final modifier.
public void process() {
    int count = 10; // Effectively final: never reassigned
    
    Runnable r1 = () -> {
        System.out.println(count); // Valid: captures effectively final variable
    };
    
    int total = 5;
    total = 6; // Reassigned: no longer effectively final
    
    // Runnable r2 = () -> System.out.println(total); // Compilation Error: total is not effectively final
}

Java Memory Model (JMM) and Initialization Safety

The final modifier has critical implications within the Java Memory Model (JMM). For instance variables, final fields provide a thread-safety guarantee known as initialization safety. When an object is constructed, the JMM ensures that any thread acquiring a reference to the fully constructed object is guaranteed to see the correctly initialized values of its final fields. This safe publication occurs without requiring explicit synchronization, provided the this reference does not escape during the constructor’s execution.
public class ImmutableData {
    private final int value;
    
    public ImmutableData(int value) {
        this.value = value;
        // JMM guarantees 'value' is visible to all threads 
        // once the constructor completes and the object is published.
    }
}

Method Parameters

The final modifier can be applied to method parameters to prevent reassignment of the parameter variable within the method body. This guarantees that the parameter strictly points to the argument passed by the caller throughout the method’s execution scope.
public void process(final int id, final User user) {
    // id = 5; // Compilation Error: final parameter cannot be reassigned
    // user = new User(); // Compilation Error: final parameter cannot be reassigned
    
    user.setActive(true); // Valid: Mutating the object state via the final reference
}
Master Java with Deep Grasping Methodology!Learn More