• final is a keyword in Java. final can be applied to variable, method, and class.

    In simple English final in java means, once a variable is assigned a value, it cannot be reassigned with another value.

  • Note that final for primitives makes them immutable and stateless. However, for wrappers, VOs, DTOs, and user-defined objects that have state, they are still mutable(Unless otherwise they are made immutable explicitly), and final has no impact on them, except that they cannot be reassigned.

    • Example:

      import lombok.Data;
      
      @Data
      @AllArgsConstructor
      class Employee {
        private String name;
        private String email;
      }
      
      public class FinalDemo {
            
        Employee getEmployee() {
          return new Employee("srk","srk@loading.com");
        }
            
        public static void main(String[] args) {
          FinalDemo fd = new FinalDemo();
          final Employee employee = fd.getEmployee();
          // Changing the state
          employee.setName("srk-real");
          employee.setEmail("srk@outlook.com");
          // employee = new Employee(); // Compiler Error:
          // The final local variable employee cannot be assigned. 
          // It must be blank and not using a compound assignment
        }
      }
      

final variable

  • final static variable - A.K.A. CONSTANT in Java.

    • class Demo {
        final static int X = 10;
      }
      
    • Or

      class Demo {
        final static int X;
        static {
          X = 10;
        }
      }
      
    • Compiler forces to initialize this var X in static block or inline. Otherwise, it says

      The blank final field X may not have been initialized

  • final instance variable

    •  class Demo {
         final int x;
         {
           x = 10;
         }
        }
      
    • Or

        class Demo {
          final int x;
          Demo(int x) {
            this.x = x;
          }
        }
      
    • Compiler forces to initialize this var x in instance block or in constructor. Otherwise, it says

      The blank final field x may not have been initialized

  • final local variable

    • class Demo {
        void m1() {
          final int x;
          System.out.println("x = " + x); // Compiler error at this line
        }
      }
      
    • Compiler forces to initialize this var x local variable. Otherwise, it says

      The local variable x may not have been initialized

    • Corrected code

      • class Demo {
          void m1() {
            final int x = 10;
            System.out.println("x = " + x);
          }
        }
        

final method

  • final methods can be overloaded, but cannot be overridden
    • Example

      class A {
        final void m1() {
          System.out.println("In void m1()");
        }
      
        final void m1(String x) {
          System.out.println("In void m1(String x)");
        }
      }
      
      class B extends A {
        void m1(String x) {} 
        // Compiler error: Cannot override the final method from A
      }
      

final class

  • final class cannot be inherited.
    • Example1

      final class A {}
      class B extends A {} 
      // Compiler error: The type B cannot subclass the final class A
      
    • Example2:

      class A {}
      final class B extends A {}
      class C extends B {} 
      // Compiler error: The type C cannot subclass the final class B
      

final as method argument

  • Example:

    class Demo {
      void m1(final int x) {
        x = 35; // Compiler error: The final local variable x cannot be assigned
      }
    }
    

Immutable Data Structure

  • Immutable data structure is a data structure whose state cannot be modified after it is created.
  • Benefits: Thread safety can be easily achieved without synchronization or locks as immutable data structure is inherently stateless.
  • Best known example in Java is java.lang.String once assigned with a value cannot be changed and any new assignment will create a new object.
    • Example:

        String x = "hello"; // create a String in constant pool
        x += "world"; // creates a new String in constant pool
      
  • Other examples:
    • List<String> fruits = List.of("apple", "grape"); // Java9 Syntax

    • List<String> fruits = new ArrayList<>(); // Pre-Java8 Syntax
      fruits.add("apple");
      fruits.add("grape");
      java.util.Collections.unmodifiableList(fruits);
      
    • Note that in both the above examples, the returned list is immutable, any attempt to add or remove elements from the list results in java.lang.UnsupportedOperationException
  • Immutable Class Example

    final class Employee {
        
      private final String name;
      private final String email;
    
      public Employee(String name, String email) {
        this.name = name;
        this.email = email;
      }
        
      // NO mutators to prevent state change
      // Can have accessors to expose readonly view
    }
    
    • Here class is final to prevent inheritance and no mutators to restrict the state change, and all fields are final to ensure that the state is defined during the creation of the object and change in state requires create of new object.

    • Alternatively, we can use Java records which are introduced in Java16. But it is shallowly immutable meaning works with primitives, but if it has fields like Employee type, the onus is on Employee class creator to properly make its state as immutable. Read official Javadoc on Java16 Records

References:

  1. Wikipedia
  2. How to create immutable class in Java SO Answer