Skip to content

The final Keyword in Java

Question

Explain the final keyword in Java and its various uses. How does it affect classes, methods, variables, and parameters? What are the best practices for using the final keyword?

Answer

The final keyword in Java is used to restrict the modification of classes, methods, variables, and parameters. It provides immutability and helps prevent inheritance or modification where not desired.

Final Variables

  1. Final Instance Variables

    public class Person {
        private final String name;
        private final int age;
    
        public Person(String name, int age) {
            this.name = name;  // Must be initialized in constructor
            this.age = age;    // Must be initialized in constructor
        }
    
        // Cannot modify final variables
        public void setName(String newName) {
            this.name = newName;  // Compilation error
        }
    }
    

  2. Final Static Variables (Constants)

    public class MathConstants {
        public static final double PI = 3.14159;
        public static final double E = 2.71828;
    
        // Static final variables must be initialized at declaration
        // or in a static block
        public static final int MAX_VALUE;
    
        static {
            MAX_VALUE = 100;
        }
    }
    

Final Methods

  1. Final Instance Methods

    public class Animal {
        public final void makeSound() {
            System.out.println("Some sound");
        }
    }
    
    public class Dog extends Animal {
        @Override
        public void makeSound() {  // Compilation error
            System.out.println("Woof!");
        }
    }
    

  2. Final Static Methods

    public class Utility {
        public static final void validateInput(String input) {
            if (input == null || input.trim().isEmpty()) {
                throw new IllegalArgumentException("Invalid input");
            }
        }
    }
    

Final Classes

  1. Final Class Definition
    public final class String {  // java.lang.String is final
        private final char[] value;
    
        public String(char[] value) {
            this.value = value.clone();
        }
    
        // Cannot be extended
    }
    
    // This would cause a compilation error
    public class MyString extends String {
        // Cannot extend final class
    }
    

Final Parameters

  1. Final Method Parameters

    public class Calculator {
        public int calculate(final int x, final int y) {
            x = 10;  // Compilation error
            y = 20;  // Compilation error
            return x + y;
        }
    }
    

  2. Final Lambda Parameters

    public class LambdaExample {
        public void processNumbers(List<Integer> numbers) {
            numbers.forEach((final Integer num) -> {
                // Cannot modify num
                System.out.println(num);
            });
        }
    }
    

Best Practices

  1. Immutability

    public final class ImmutablePerson {
        private final String name;
        private final List<String> hobbies;
    
        public ImmutablePerson(String name, List<String> hobbies) {
            this.name = name;
            this.hobbies = new ArrayList<>(hobbies);  // Defensive copy
        }
    
        public String getName() {
            return name;
        }
    
        public List<String> getHobbies() {
            return Collections.unmodifiableList(hobbies);  // Unmodifiable view
        }
    }
    

  2. Performance Optimization

    public class OptimizedClass {
        private final int[] data;
    
        public OptimizedClass(int[] data) {
            this.data = data.clone();
        }
    
        // JVM can optimize final fields
        public int getData(int index) {
            return data[index];
        }
    }
    

Common Use Cases

  1. Thread Safety

    public class ThreadSafeCounter {
        private final AtomicInteger count = new AtomicInteger(0);
    
        public void increment() {
            count.incrementAndGet();
        }
    
        public int getCount() {
            return count.get();
        }
    }
    

  2. Configuration Values

    public class Configuration {
        public static final String DATABASE_URL = "jdbc:mysql://localhost:3306/db";
        public static final int MAX_CONNECTIONS = 10;
        public static final int TIMEOUT_SECONDS = 30;
    }
    

Testing

  1. Testing Final Classes

    @Test
    public void testImmutablePerson() {
        List<String> hobbies = Arrays.asList("Reading", "Gaming");
        ImmutablePerson person = new ImmutablePerson("John", hobbies);
    
        // Verify immutability
        List<String> modifiedHobbies = person.getHobbies();
        modifiedHobbies.add("Swimming");  // UnsupportedOperationException
    
        assertEquals("John", person.getName());
        assertEquals(2, person.getHobbies().size());
    }
    

  2. Testing Final Methods

    @Test
    public void testFinalMethod() {
        Animal animal = new Animal();
        animal.makeSound();  // Should print "Some sound"
    
        // Verify method cannot be overridden
        Dog dog = new Dog();
        dog.makeSound();  // Should still print "Some sound"
    }
    

Common Pitfalls

  1. Reference Immutability

    public class MutableReference {
        private final List<String> list = new ArrayList<>();
    
        public void addItem(String item) {
            list.add(item);  // Allowed - modifying object, not reference
        }
    
        public void reassignList() {
            list = new ArrayList<>();  // Compilation error
        }
    }
    

  2. Constructor Initialization

    public class InitializationError {
        private final int value;  // Must be initialized
    
        public InitializationError() {
            // Missing initialization - compilation error
        }
    }