What is the purpose of access modifiers?

Learn what is the purpose of access modifiers? with practical examples, diagrams, and best practices. Covers java, access-modifiers development techniques with visual explanations.

Understanding Access Modifiers in Java: Controlling Visibility and Encapsulation

Hero image for What is the purpose of access modifiers?

Explore the purpose and usage of Java access modifiers (public, protected, default, private) to manage class, method, and field visibility, ensuring proper encapsulation and secure code design.

Access modifiers in Java are keywords that set the accessibility (visibility) of classes, constructors, methods, and fields. They are a fundamental aspect of object-oriented programming, primarily used to implement encapsulation, a core OOP principle. By controlling what parts of your code are accessible from where, access modifiers help in building robust, secure, and maintainable applications. This article will delve into each access modifier, explaining its scope and providing practical examples.

The Four Pillars of Access Control

Java provides four types of access modifiers: public, protected, default (no keyword), and private. Each offers a different level of visibility, ranging from completely open to strictly restricted. Understanding these levels is crucial for designing well-structured and secure applications.

flowchart TD
    A[Member] --> B{Access Modifier?}
    B -->|public| C[Accessible Everywhere]
    B -->|protected| D[Accessible within Package & Subclasses]
    B -->|default (no keyword)| E[Accessible within Package Only]
    B -->|private| F[Accessible within Class Only]

Hierarchy of Java Access Modifiers and their visibility scope.

1. Public Access Modifier

The public access modifier is the most permissive. When a class, method, or field is declared public, it can be accessed from anywhere within the same project, including other classes, packages, and even different applications that use your code. This is typically used for methods that form the public API of a class or for constants that need to be universally available.

package com.example.app;

public class PublicExample {
    public int publicVariable = 10;

    public void publicMethod() {
        System.out.println("This is a public method.");
    }
}

// In another package, e.g., com.example.anotherapp
package com.example.anotherapp;
import com.example.app.PublicExample;

public class AccessPublic {
    public static void main(String[] args) {
        PublicExample obj = new PublicExample();
        System.out.println(obj.publicVariable); // Accessible
        obj.publicMethod(); // Accessible
    }
}

Demonstrates public access from different packages.

2. Protected Access Modifier

The protected access modifier allows members to be accessed within the same package and by subclasses (even if those subclasses are in different packages). This is particularly useful when you want to provide certain functionalities to derived classes while still restricting general public access. It supports inheritance by allowing child classes to interact with parent class members.

package com.example.base;

public class ParentClass {
    protected int protectedVariable = 20;

    protected void protectedMethod() {
        System.out.println("This is a protected method.");
    }
}

// In the same package
package com.example.base;

class SamePackageClass {
    void accessProtected() {
        ParentClass parent = new ParentClass();
        System.out.println(parent.protectedVariable); // Accessible
        parent.protectedMethod(); // Accessible
    }
}

// In a different package, as a subclass
package com.example.derived;
import com.example.base.ParentClass;

public class ChildClass extends ParentClass {
    void accessProtectedFromChild() {
        System.out.println(protectedVariable); // Accessible
        protectedMethod(); // Accessible
    }
}

// In a different package, NOT a subclass
package com.example.other;
import com.example.base.ParentClass;

public class OtherClass {
    void tryAccess() {
        ParentClass parent = new ParentClass();
        // System.out.println(parent.protectedVariable); // Compile-time error
        // parent.protectedMethod(); // Compile-time error
    }
}

Illustrates protected access within the same package and by subclasses.

3. Default (Package-Private) Access Modifier

When no access modifier is specified for a class, method, or field, it is considered to have default or package-private access. This means the member is only accessible within its own package. It's a good choice for utility classes or helper methods that are only relevant to other components within the same package and should not be exposed externally.

package com.example.data;

class DefaultExample {
    int defaultVariable = 30; // Default access

    void defaultMethod() { // Default access
        System.out.println("This is a default method.");
    }
}

// In the same package
package com.example.data;

public class AccessDefault {
    public static void main(String[] args) {
        DefaultExample obj = new DefaultExample();
        System.out.println(obj.defaultVariable); // Accessible
        obj.defaultMethod(); // Accessible
    }
}

// In a different package
package com.example.utils;
// import com.example.data.DefaultExample; // Cannot import default class

public class TryAccessDefault {
    public static void main(String[] args) {
        // DefaultExample obj = new DefaultExample(); // Compile-time error
        // obj.defaultMethod(); // Compile-time error
    }
}

Shows default access, restricted to the same package.

4. Private Access Modifier

The private access modifier is the most restrictive. Members declared private are only accessible from within the class in which they are declared. They cannot be accessed from outside the class, not even by subclasses or classes within the same package. This is the cornerstone of strong encapsulation, hiding implementation details and exposing functionality only through public methods (getters/setters).

package com.example.model;

public class Account {
    private double balance = 1000.0; // Private variable

    private void logTransaction(String type, double amount) {
        System.out.println("Transaction: " + type + ", Amount: " + amount);
    }

    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            logTransaction("Deposit", amount);
        }
    }

    public double getBalance() {
        return balance;
    }
}

// In another class (even in the same package)
package com.example.model;

public class AccessAccount {
    public static void main(String[] args) {
        Account myAccount = new Account();
        // System.out.println(myAccount.balance); // Compile-time error
        // myAccount.logTransaction("Withdraw", 50); // Compile-time error

        myAccount.deposit(200); // Accessible via public method
        System.out.println("Current Balance: " + myAccount.getBalance());
    }
}

Demonstrates private access, only within the declaring class.

Summary of Access Levels

The following table summarizes the visibility of each access modifier across different scopes. This overview helps in quickly deciding which modifier to use for a given member.

Hero image for What is the purpose of access modifiers?

Access Modifier Visibility Summary

Choosing the correct access modifier is a critical design decision that impacts the maintainability, extensibility, and security of your Java applications. By carefully controlling visibility, you can prevent unintended modifications, hide complex implementation details, and create clear, stable APIs for your classes.