Skip to content

The final Keyword in Java

Question

Explain the different uses of the final keyword in Java, including its application to variables, methods, and classes. What are the implications and best practices for using final?

Answer

The final keyword in Java is used to restrict the modification of variables, methods, and classes. It's a powerful tool for creating immutable objects and preventing inheritance or method overriding.

1. Final Variables

Local Variables

public class FinalVariableExample {
    public void method() {
        final int x = 10;  // Must be initialized
        // x = 20;  // Compilation error
    }
}

Instance Variables

public class FinalInstanceVariable {
    private final int value;  // Must be initialized in constructor or declaration

    public FinalInstanceVariable(int value) {
        this.value = value;  // Initialize in constructor
    }

    // value = 20;  // Compilation error
}

Static Variables

public class FinalStaticVariable {
    private static final double PI = 3.14159;  // Must be initialized at declaration
    // PI = 3.14;  // Compilation error
}

2. Final Methods

public class FinalMethodExample {
    class Parent {
        public final void method() {
            System.out.println("Cannot be overridden");
        }
    }

    class Child extends Parent {
        // @Override
        // public void method() {}  // Compilation error
    }
}

3. Final Classes

public final class FinalClass {
    // Cannot be extended
}

// class Child extends FinalClass {}  // Compilation error

4. Final Parameters

public class FinalParameter {
    public void process(final String input) {
        // input = "new value";  // Compilation error
        System.out.println(input);
    }
}

5. Final Reference Variables

public class FinalReference {
    public static void main(String[] args) {
        final StringBuilder sb = new StringBuilder("Hello");
        sb.append(" World");  // Valid - modifying object
        // sb = new StringBuilder();  // Invalid - changing reference
    }
}

6. Final and Immutability

Creating Immutable Objects

public final class ImmutablePerson {
    private final String name;
    private final int age;
    private final List<String> hobbies;

    public ImmutablePerson(String name, int age, List<String> hobbies) {
        this.name = name;
        this.age = age;
        this.hobbies = new ArrayList<>(hobbies);  // Defensive copy
    }

    public String getName() { return name; }
    public int getAge() { return age; }
    public List<String> getHobbies() {
        return Collections.unmodifiableList(hobbies);  // Unmodifiable view
    }
}

7. Final in Anonymous Classes

public class AnonymousClass {
    public static void main(String[] args) {
        final int x = 10;  // Must be final to use in anonymous class
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println(x);  // Can access final variable
            }
        };
    }
}

8. Final and Lambda Expressions

public class LambdaExample {
    public static void main(String[] args) {
        final int x = 10;  // Must be final or effectively final
        Runnable r = () -> System.out.println(x);
    }
}

9. Final and Thread Safety

public class ThreadSafeExample {
    private final Map<String, String> cache = new HashMap<>();

    public void initializeCache() {
        cache.put("key1", "value1");
        cache.put("key2", "value2");
    }

    public String getValue(String key) {
        return cache.get(key);
    }
}

10. Best Practices

  1. Use final for Constants

    public class Constants {
        public static final int MAX_RETRY_ATTEMPTS = 3;
        public static final String DEFAULT_ENCODING = "UTF-8";
    }
    

  2. Use final for Immutable Objects

    public final class ImmutablePoint {
        private final int x;
        private final int y;
    
        public ImmutablePoint(int x, int y) {
            this.x = x;
            this.y = y;
        }
    
        public int getX() { return x; }
        public int getY() { return y; }
    }
    

  3. Use final for Method Parameters

    public class ParameterExample {
        public void process(final String input) {
            // Prevents accidental modification
            System.out.println(input);
        }
    }
    

11. Common Pitfalls

  1. Final Reference Variables

    public class FinalReferencePitfall {
        public static void main(String[] args) {
            final List<String> list = new ArrayList<>();
            list.add("item");  // Valid
            // list = new ArrayList<>();  // Invalid
        }
    }
    

  2. Final and Collections

    public class CollectionPitfall {
        private final List<String> list = new ArrayList<>();
    
        public List<String> getList() {
            return list;  // Returns mutable reference
        }
    
        // Better approach
        public List<String> getList() {
            return Collections.unmodifiableList(list);
        }
    }
    

12. Performance Considerations

  1. JVM Optimization

    public class OptimizationExample {
        private final int value = 42;
    
        public int getValue() {
            return value;  // May be inlined by JVM
        }
    }
    

  2. Thread Safety

    public class ThreadSafetyExample {
        private final Object lock = new Object();
    
        public void synchronizedMethod() {
            synchronized(lock) {
                // Thread-safe code
            }
        }
    }
    

13. Design Patterns Using final

  1. Singleton Pattern

    public class Singleton {
        private static final Singleton INSTANCE = new Singleton();
    
        private Singleton() {}
    
        public static Singleton getInstance() {
            return INSTANCE;
        }
    }
    

  2. Builder Pattern ```java public class Person { private final String name; private final int age;

    private Person(Builder builder) { this.name = builder.name; this.age = builder.age; }

    public static class Builder { private String name; private int age;

       public Builder name(String name) {
           this.name = name;
           return this;
       }
    
       public Builder age(int age) {
           this.age = age;
           return this;
       }
    
       public Person build() {
           return new Person(this);
       }
    

    } }