🏗️ Non-Access Modifiers in Java

• Originally published on old blog

Note: This post was originally published in 2014. While the core concepts remain valid, some examples may use older Java syntax. The principles discussed are still fundamental to Java programming.

Non-access modifiers in Java are keywords that modify the behavior of classes, methods, and variables without controlling their access level. Understanding these modifiers is crucial for writing efficient and well-structured Java code.

1. Static Modifier

The static modifier indicates that a member belongs to the class itself rather than to instances of the class.

Static Variables

public class Counter {
    static int count = 0;  // Shared across all instances
    
    public Counter() {
        count++;
    }
    
    public static int getCount() {
        return count;
    }
}

Static Methods

public class MathUtils {
    public static double calculateArea(double radius) {
        return Math.PI * radius * radius;
    }
    
    public static int max(int a, int b) {
        return (a > b) ? a : b;
    }
}

2. Final Modifier

The final modifier prevents further modification, inheritance, or overriding.

Final Variables

public class Constants {
    public static final double PI = 3.14159;
    public static final String APP_NAME = "MyApplication";
    
    public void processData() {
        final int maxRetries = 3;  // Local final variable
        // maxRetries = 5;  // This would cause compilation error
    }
}

Final Methods

public class Parent {
    public final void displayInfo() {
        System.out.println("This method cannot be overridden");
    }
}

public class Child extends Parent {
    // This would cause compilation error:
    // public void displayInfo() { }
}

Final Classes

public final class StringUtils {
    public static String reverse(String str) {
        return new StringBuilder(str).reverse().toString();
    }
}

// This would cause compilation error:
// public class ExtendedStringUtils extends StringUtils { }

3. Abstract Modifier

The abstract modifier is used for classes and methods that are incomplete and must be implemented by subclasses.

Abstract Classes

public abstract class Shape {
    protected double area;
    
    public abstract double calculateArea();
    
    public void displayArea() {
        System.out.println("Area: " + calculateArea());
    }
}

public class Circle extends Shape {
    private double radius;
    
    public Circle(double radius) {
        this.radius = radius;
    }
    
    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

Abstract Methods

public abstract class DatabaseConnection {
    protected String url;
    protected String username;
    protected String password;
    
    public abstract void connect();
    public abstract void disconnect();
    public abstract void executeQuery(String query);
}

4. Synchronized Modifier

The synchronized modifier provides thread safety by ensuring that only one thread can execute a method or block at a time.

Synchronized Methods

public class BankAccount {
    private double balance;
    
    public synchronized void deposit(double amount) {
        balance += amount;
        System.out.println("Deposited: " + amount + ", New balance: " + balance);
    }
    
    public synchronized void withdraw(double amount) {
        if (balance >= amount) {
            balance -= amount;
            System.out.println("Withdrawn: " + amount + ", New balance: " + balance);
        } else {
            System.out.println("Insufficient funds");
        }
    }
}

Synchronized Blocks

public class Cache {
    private Map<String, Object> cache = new HashMap<>();
    private final Object lock = new Object();
    
    public void put(String key, Object value) {
        synchronized (lock) {
            cache.put(key, value);
        }
    }
    
    public Object get(String key) {
        synchronized (lock) {
            return cache.get(key);
        }
    }
}

5. Transient Modifier

The transient modifier indicates that a field should not be serialized when the object is written to a stream.

import java.io.Serializable;

public class User implements Serializable {
    private String username;
    private String password;
    private transient String sessionToken;  // Won't be serialized
    private transient long lastLoginTime;   // Won't be serialized
    
    public User(String username, String password) {
        this.username = username;
        this.password = password;
        this.sessionToken = generateSessionToken();
        this.lastLoginTime = System.currentTimeMillis();
    }
    
    private String generateSessionToken() {
        return "token_" + System.currentTimeMillis();
    }
}

6. Volatile Modifier

The volatile modifier ensures that a variable's value is always read from and written to main memory, not from thread-local cache.

public class SharedCounter {
    private volatile int count = 0;
    
    public void increment() {
        count++;
    }
    
    public int getCount() {
        return count;
    }
}

public class Worker implements Runnable {
    private SharedCounter counter;
    private volatile boolean running = true;
    
    public void stop() {
        running = false;
    }
    
    @Override
    public void run() {
        while (running) {
            counter.increment();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }
}

Best Practices

  • Use final for constants and immutable objects
  • Use static for utility methods and shared data
  • Use abstract for defining contracts and common behavior
  • Use synchronized carefully to avoid performance issues
  • Use transient for fields that shouldn't be serialized
  • Use volatile for simple thread-safe flags

Common Pitfalls

  • Overusing synchronized can lead to performance bottlenecks
  • Forgetting to implement abstract methods in subclasses
  • Using volatile for complex operations (use synchronized instead)
  • Not understanding that static members are shared across all instances

Key Takeaways

  • Non-access modifiers control behavior, not access
  • Each modifier serves a specific purpose in Java programming
  • Understanding these modifiers is essential for writing thread-safe and efficient code
  • Choose the right modifier based on your specific requirements