1. java
  2. /oop
  3. /static-final

Master Java Static and Final Keywords

Java Static and Final Keywords

The static and final keywords in Java are powerful modifiers that affect how classes, methods, and variables behave. Understanding these keywords is essential for effective Java programming and object-oriented design.

The Static Keyword

The static keyword belongs to the class rather than any instance of the class. Static members are shared among all instances and can be accessed without creating an object.

Static Variables (Class Variables)

Static variables are shared by all instances of a class:

public class Counter {
    private static int totalCount = 0; // Static variable
    private int instanceCount; // Instance variable
    
    public Counter() {
        totalCount++; // Increment static counter
        instanceCount++; // Increment instance counter
    }
    
    public static int getTotalCount() {
        return totalCount;
    }
    
    public int getInstanceCount() {
        return instanceCount;
    }
    
    public static void resetTotalCount() {
        totalCount = 0;
    }
}

// Usage
public class CounterDemo {
    public static void main(String[] args) {
        System.out.println("Initial total count: " + Counter.getTotalCount()); // 0
        
        Counter c1 = new Counter();
        Counter c2 = new Counter();
        Counter c3 = new Counter();
        
        System.out.println("Total count: " + Counter.getTotalCount()); // 3
        System.out.println("c1 instance count: " + c1.getInstanceCount()); // 1
        System.out.println("c2 instance count: " + c2.getInstanceCount()); // 1
        
        // Static variable is shared among all instances
        System.out.println("Total via c1: " + Counter.getTotalCount()); // 3
        System.out.println("Total via c2: " + Counter.getTotalCount()); // 3
        
        Counter.resetTotalCount();
        System.out.println("After reset: " + Counter.getTotalCount()); // 0
    }
}

Static Methods

Static methods belong to the class and can be called without creating an instance:

public class MathUtils {
    // Static methods - utility functions
    public static int add(int a, int b) {
        return a + b;
    }
    
    public static double calculateCircleArea(double radius) {
        return Math.PI * radius * radius;
    }
    
    public static int factorial(int n) {
        if (n <= 1) return 1;
        return n * factorial(n - 1); // Recursive static method call
    }
    
    public static boolean isPrime(int number) {
        if (number <= 1) return false;
        if (number <= 3) return true;
        if (number % 2 == 0 || number % 3 == 0) return false;
        
        for (int i = 5; i * i <= number; i += 6) {
            if (number % i == 0 || number % (i + 2) == 0) {
                return false;
            }
        }
        return true;
    }
    
    // Private constructor to prevent instantiation
    private MathUtils() {
        throw new UnsupportedOperationException("Utility class should not be instantiated");
    }
}

// Usage
public class MathUtilsDemo {
    public static void main(String[] args) {
        // Call static methods without creating instance
        System.out.println("5 + 3 = " + MathUtils.add(5, 3));
        System.out.println("Circle area (r=5): " + MathUtils.calculateCircleArea(5.0));
        System.out.println("5! = " + MathUtils.factorial(5));
        System.out.println("Is 17 prime? " + MathUtils.isPrime(17));
    }
}

Static Blocks

Static blocks are executed when the class is first loaded:

public class DatabaseConfig {
    private static String databaseUrl;
    private static String username;
    private static String password;
    
    // Static block - executed once when class is loaded
    static {
        System.out.println("Loading database configuration...");
        databaseUrl = loadProperty("db.url", "jdbc:mysql://localhost:3306/mydb");
        username = loadProperty("db.username", "admin");
        password = loadProperty("db.password", "password");
        System.out.println("Database configuration loaded");
    }
    
    // Another static block - executed in order
    static {
        System.out.println("Validating database configuration...");
        if (databaseUrl == null || username == null || password == null) {
            throw new RuntimeException("Database configuration is incomplete");
        }
        System.out.println("Database configuration validated");
    }
    
    private static String loadProperty(String key, String defaultValue) {
        // Simulate loading from properties file
        System.out.println("Loading property: " + key);
        return defaultValue;
    }
    
    public static String getDatabaseUrl() {
        return databaseUrl;
    }
    
    public static String getUsername() {
        return username;
    }
    
    // Don't expose password directly in real applications
    public static boolean validatePassword(String inputPassword) {
        return password.equals(inputPassword);
    }
}

// Usage
public class DatabaseConfigDemo {
    public static void main(String[] args) {
        // Static blocks execute when class is first accessed
        System.out.println("Accessing DatabaseConfig for first time:");
        String url = DatabaseConfig.getDatabaseUrl();
        System.out.println("Database URL: " + url);
        
        // Subsequent access doesn't trigger static blocks
        System.out.println("Second access:");
        String username = DatabaseConfig.getUsername();
        System.out.println("Username: " + username);
    }
}

Static Inner Classes

Static nested classes don't need a reference to the outer class:

public class OuterClass {
    private static String staticField = "Static field in outer class";
    private String instanceField = "Instance field in outer class";
    
    // Static nested class
    public static class StaticNestedClass {
        private String nestedField = "Field in static nested class";
        
        public void display() {
            // Can access static members of outer class
            System.out.println("Accessing: " + staticField);
            
            // Cannot access instance members directly
            // System.out.println(instanceField); // Compilation error
            
            // Can access instance members through object reference
            OuterClass outer = new OuterClass();
            System.out.println("Accessing through reference: " + outer.instanceField);
        }
        
        public static void staticMethod() {
            System.out.println("Static method in static nested class");
        }
    }
    
    // Non-static nested class for comparison
    public class InnerClass {
        public void display() {
            // Can access both static and instance members
            System.out.println("Static: " + staticField);
            System.out.println("Instance: " + instanceField);
        }
    }
    
    public static void main(String[] args) {
        // Create static nested class without outer class instance
        StaticNestedClass nested = new StaticNestedClass();
        nested.display();
        StaticNestedClass.staticMethod();
        
        // Create inner class - requires outer class instance
        OuterClass outer = new OuterClass();
        InnerClass inner = outer.new InnerClass();
        inner.display();
    }
}

The Final Keyword

The final keyword restricts modification and inheritance. It can be applied to variables, methods, and classes.

Final Variables

Final variables cannot be reassigned once initialized:

public class FinalVariableExample {
    // Final instance variable - must be initialized
    private final String name;
    private final int id;
    
    // Final static variable (constant)
    public static final double PI = 3.14159;
    public static final String APP_NAME = "MyApplication";
    
    // Final array - reference is final, but contents can be modified
    private final int[] numbers = {1, 2, 3, 4, 5};
    
    // Final collection
    private final List<String> items = new ArrayList<>();
    
    public FinalVariableExample(String name, int id) {
        this.name = name; // Can initialize final variable in constructor
        this.id = id;
        
        // this.name = "Changed"; // Compilation error - cannot reassign
    }
    
    public void demonstrateFinalVariables() {
        // Final local variable
        final int localConstant = 100;
        // localConstant = 200; // Compilation error
        
        // Final reference - object reference cannot change
        final StringBuilder sb = new StringBuilder("Hello");
        // sb = new StringBuilder("World"); // Compilation error
        
        // But object state can be modified
        sb.append(" World"); // This is allowed
        System.out.println(sb.toString()); // "Hello World"
        
        // Final array reference cannot change
        // numbers = new int[]{6, 7, 8}; // Compilation error
        
        // But array contents can be modified
        numbers[0] = 10; // This is allowed
        System.out.println("Modified array: " + Arrays.toString(numbers));
        
        // Final collection reference cannot change
        // items = new ArrayList<>(); // Compilation error
        
        // But collection contents can be modified
        items.add("Item1");
        items.add("Item2");
        System.out.println("Items: " + items);
    }
    
    public String getName() {
        return name;
    }
    
    public int getId() {
        return id;
    }
}

Final Methods

Final methods cannot be overridden by subclasses:

public class BaseClass {
    // Regular method - can be overridden
    public void regularMethod() {
        System.out.println("Base class regular method");
    }
    
    // Final method - cannot be overridden
    public final void finalMethod() {
        System.out.println("Base class final method");
    }
    
    // Protected final method
    protected final void protectedFinalMethod() {
        System.out.println("Protected final method");
    }
    
    // Final method with template pattern
    public final void templateMethod() {
        initialize();
        process();
        cleanup();
    }
    
    // Hook methods that subclasses can override
    protected void initialize() {
        System.out.println("Base initialization");
    }
    
    protected void process() {
        System.out.println("Base processing");
    }
    
    protected void cleanup() {
        System.out.println("Base cleanup");
    }
}

public class SubClass extends BaseClass {
    @Override
    public void regularMethod() {
        System.out.println("Subclass regular method");
    }
    
    // Cannot override final method
    /*
    @Override
    public void finalMethod() { // Compilation error
        System.out.println("Attempting to override final method");
    }
    */
    
    @Override
    protected void initialize() {
        System.out.println("Subclass initialization");
    }
    
    @Override
    protected void process() {
        System.out.println("Subclass processing");
    }
    
    // Can access final method from parent
    public void useParentFinalMethod() {
        finalMethod(); // Calls parent's final method
        protectedFinalMethod(); // Calls parent's protected final method
    }
}

// Usage
public class FinalMethodDemo {
    public static void main(String[] args) {
        SubClass sub = new SubClass();
        sub.regularMethod(); // Calls overridden method
        sub.finalMethod(); // Calls parent's final method
        sub.templateMethod(); // Uses template pattern with overridden hooks
        sub.useParentFinalMethod();
    }
}

Final Classes

Final classes cannot be extended:

// Final class - cannot be subclassed
public final class ImmutablePoint {
    private final double x;
    private final double y;
    
    public ImmutablePoint(double x, double y) {
        this.x = x;
        this.y = y;
    }
    
    public double getX() {
        return x;
    }
    
    public double getY() {
        return y;
    }
    
    public ImmutablePoint add(ImmutablePoint other) {
        return new ImmutablePoint(this.x + other.x, this.y + other.y);
    }
    
    public double distanceTo(ImmutablePoint other) {
        double dx = this.x - other.x;
        double dy = this.y - other.y;
        return Math.sqrt(dx * dx + dy * dy);
    }
    
    @Override
    public String toString() {
        return "Point(" + x + ", " + y + ")";
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        ImmutablePoint point = (ImmutablePoint) obj;
        return Double.compare(point.x, x) == 0 && Double.compare(point.y, y) == 0;
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }
}

// Cannot extend final class
/*
public class ExtendedPoint extends ImmutablePoint { // Compilation error
    // Cannot extend final class
}
*/

// Real-world examples of final classes
public class FinalClassExamples {
    public static void main(String[] args) {
        // String is a final class
        String str = "Hello";
        // String cannot be extended
        
        // Integer is a final class
        Integer num = 42;
        // Integer cannot be extended
        
        // Many wrapper classes are final: Boolean, Character, Byte, Short, Long, Float, Double
        
        ImmutablePoint p1 = new ImmutablePoint(3, 4);
        ImmutablePoint p2 = new ImmutablePoint(6, 8);
        
        System.out.println("Point 1: " + p1);
        System.out.println("Point 2: " + p2);
        System.out.println("Distance: " + p1.distanceTo(p2));
        System.out.println("Sum: " + p1.add(p2));
    }
}

Combining Static and Final

When combined, static final creates constants:

public class Constants {
    // Mathematical constants
    public static final double PI = 3.14159265358979323846;
    public static final double E = 2.71828182845904523536;
    public static final double GOLDEN_RATIO = 1.61803398874989484820;
    
    // Application constants
    public static final String APP_NAME = "MyApplication";
    public static final String VERSION = "1.0.0";
    public static final int MAX_USERS = 1000;
    
    // Configuration constants
    public static final int DEFAULT_TIMEOUT = 30000; // milliseconds
    public static final int MAX_RETRY_ATTEMPTS = 3;
    public static final String DEFAULT_CHARSET = "UTF-8";
    
    // Collections as constants (immutable)
    public static final List<String> SUPPORTED_FORMATS = 
        Collections.unmodifiableList(Arrays.asList("JSON", "XML", "CSV"));
    
    public static final Set<String> VALID_STATUSES = 
        Collections.unmodifiableSet(new HashSet<>(Arrays.asList("ACTIVE", "INACTIVE", "PENDING")));
    
    public static final Map<String, Integer> HTTP_STATUS_CODES = 
        Collections.unmodifiableMap(new HashMap<String, Integer>() {{
            put("OK", 200);
            put("NOT_FOUND", 404);
            put("INTERNAL_ERROR", 500);
        }});
    
    // Private constructor to prevent instantiation
    private Constants() {
        throw new UnsupportedOperationException("Constants class should not be instantiated");
    }
}

// Enum alternative for constants
public enum HttpStatus {
    OK(200, "OK"),
    NOT_FOUND(404, "Not Found"),
    INTERNAL_ERROR(500, "Internal Server Error");
    
    private final int code;
    private final String message;
    
    private HttpStatus(int code, String message) {
        this.code = code;
        this.message = message;
    }
    
    public int getCode() {
        return code;
    }
    
    public String getMessage() {
        return message;
    }
}

Practical Examples

Singleton Pattern with Static and Final

public final class DatabaseManager {
    private static final DatabaseManager INSTANCE = new DatabaseManager();
    private final String connectionString;
    
    private DatabaseManager() {
        this.connectionString = "jdbc:mysql://localhost:3306/mydb";
        initialize();
    }
    
    public static DatabaseManager getInstance() {
        return INSTANCE;
    }
    
    private void initialize() {
        System.out.println("Initializing database connection...");
    }
    
    public final void connect() {
        System.out.println("Connecting to database: " + connectionString);
    }
    
    public final void disconnect() {
        System.out.println("Disconnecting from database");
    }
}

Utility Class Pattern

public final class StringUtils {
    // Private constructor prevents instantiation
    private StringUtils() {
        throw new UnsupportedOperationException("Utility class");
    }
    
    public static boolean isEmpty(String str) {
        return str == null || str.isEmpty();
    }
    
    public static boolean isBlank(String str) {
        return str == null || str.trim().isEmpty();
    }
    
    public static String capitalize(String str) {
        if (isEmpty(str)) return str;
        return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase();
    }
    
    public static String reverse(String str) {
        if (isEmpty(str)) return str;
        return new StringBuilder(str).reverse().toString();
    }
    
    public static int countOccurrences(String str, char ch) {
        if (isEmpty(str)) return 0;
        return (int) str.chars().filter(c -> c == ch).count();
    }
}

Builder Pattern with Final Fields

public final class User {
    private final String username;
    private final String email;
    private final String firstName;
    private final String lastName;
    private final int age;
    private final boolean active;
    
    private User(Builder builder) {
        this.username = builder.username;
        this.email = builder.email;
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
        this.age = builder.age;
        this.active = builder.active;
    }
    
    // Getters only - no setters for immutability
    public String getUsername() { return username; }
    public String getEmail() { return email; }
    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
    public int getAge() { return age; }
    public boolean isActive() { return active; }
    
    public static class Builder {
        private final String username; // Required
        private final String email; // Required
        
        private String firstName = "";
        private String lastName = "";
        private int age = 0;
        private boolean active = true;
        
        public Builder(String username, String email) {
            this.username = username;
            this.email = email;
        }
        
        public Builder firstName(String firstName) {
            this.firstName = firstName;
            return this;
        }
        
        public Builder lastName(String lastName) {
            this.lastName = lastName;
            return this;
        }
        
        public Builder age(int age) {
            this.age = age;
            return this;
        }
        
        public Builder active(boolean active) {
            this.active = active;
            return this;
        }
        
        public User build() {
            return new User(this);
        }
    }
    
    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", email='" + email + '\'' +
                ", firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", age=" + age +
                ", active=" + active +
                '}';
    }
}

// Usage
public class UserDemo {
    public static void main(String[] args) {
        User user = new User.Builder("john_doe", "[email protected]")
                .firstName("John")
                .lastName("Doe")
                .age(30)
                .active(true)
                .build();
        
        System.out.println(user);
    }
}

Best Practices

1. Use Static for Utility Methods

public final class FileUtils {
    private FileUtils() {} // Prevent instantiation
    
    public static boolean exists(String path) {
        return new File(path).exists();
    }
    
    public static String readFile(String path) throws IOException {
        return Files.readString(Paths.get(path));
    }
    
    public static void writeFile(String path, String content) throws IOException {
        Files.writeString(Paths.get(path), content);
    }
}

2. Use Final for Immutable Classes

public final class Money {
    private final BigDecimal amount;
    private final Currency currency;
    
    public Money(BigDecimal amount, Currency currency) {
        this.amount = amount;
        this.currency = currency;
    }
    
    public Money add(Money other) {
        if (!this.currency.equals(other.currency)) {
            throw new IllegalArgumentException("Currency mismatch");
        }
        return new Money(this.amount.add(other.amount), this.currency);
    }
    
    // More immutable operations...
}

3. Use Static Final for Constants

public class GameConfig {
    public static final int MAX_PLAYERS = 4;
    public static final double GRAVITY = 9.81;
    public static final String GAME_TITLE = "Super Game";
    
    // Use enum for related constants
    public enum Difficulty {
        EASY(1), MEDIUM(2), HARD(3);
        
        private final int level;
        
        Difficulty(int level) {
            this.level = level;
        }
        
        public int getLevel() {
            return level;
        }
    }
}

Common Pitfalls

1. Final Collections

public class CollectionPitfall {
    // The reference is final, not the contents
    private static final List<String> ITEMS = new ArrayList<>();
    
    static {
        ITEMS.add("Item1");
        ITEMS.add("Item2");
    }
    
    public static void main(String[] args) {
        // This is allowed - modifying contents
        ITEMS.add("Item3");
        ITEMS.remove("Item1");
        
        // This would cause compilation error - changing reference
        // ITEMS = new ArrayList<>();
    }
}

// Better approach for truly immutable collections
public class ImmutableCollections {
    public static final List<String> ITEMS = 
        Collections.unmodifiableList(Arrays.asList("Item1", "Item2"));
    
    public static void main(String[] args) {
        // This will throw UnsupportedOperationException at runtime
        try {
            ITEMS.add("Item3");
        } catch (UnsupportedOperationException e) {
            System.out.println("Cannot modify immutable collection");
        }
    }
}

2. Static Method Overriding

public class Parent {
    public static void staticMethod() {
        System.out.println("Parent static method");
    }
}

public class Child extends Parent {
    // This is method hiding, not overriding
    public static void staticMethod() {
        System.out.println("Child static method");
    }
}

public class StaticMethodDemo {
    public static void main(String[] args) {
        Parent parent = new Parent();
        Child child = new Child();
        Parent polymorphic = new Child();
        
        parent.staticMethod();     // "Parent static method"
        child.staticMethod();      // "Child static method"
        polymorphic.staticMethod(); // "Parent static method" - no polymorphism!
        
        // Better to call static methods on class names
        Parent.staticMethod();     // "Parent static method"
        Child.staticMethod();      // "Child static method"
    }
}

Summary

The static and final keywords provide important capabilities:

Static Keyword:

  • Class-level: Belongs to class, not instances
  • Shared State: Static variables shared among all instances
  • Utility Methods: Static methods for functionality that doesn't need instance data
  • Memory Efficiency: One copy per class, not per instance
  • Early Loading: Static blocks execute when class is loaded

Final Keyword:

  • Immutability: Final variables cannot be reassigned
  • Template Method: Final methods prevent overriding
  • Class Sealing: Final classes prevent inheritance
  • Constants: Static final for class-level constants
  • Security: Prevents modification and subclassing

Best Practices:

  • Use static for utility classes and shared state
  • Use final for immutable classes and constants
  • Combine static final for class constants
  • Make utility classes final with private constructors
  • Use final methods for template patterns
  • Prefer enum over static final constants for related values

Understanding static and final enables better design patterns, immutability, and efficient memory usage in Java applications.