Access Modifiers & Visibility
In Java, access modifiers are keywords that set the accessibility (visibility) of classes, constructors, methods, and fields. They are a fundamental aspect of encapsulation, an object-oriented programming principle that allows you to hide internal implementation details and control how other parts of the code can interact with your classes and their members.
There are four types of access modifiers in Java:
public
protected
default
(or package-private)private
These modifiers specify who can access a member: the class itself, other classes in the same package, subclasses (even in different packages), or all classes.
1. public
The public
access modifier is the most permissive. When a class, method, or field is declared public
, it means it is accessible from anywhere in the application.
- Classes: A
public
class can be accessed by any other class. - Methods/Fields/Constructors:
public
members can be accessed from any class, regardless of the package.
Visibility: Accessible everywhere.
Example:
// package com.example.model
package com.example.model;
public class PublicClass { // Public class
public int publicField = 10; // Public field
public PublicClass() { // Public constructor
System.out.println("PublicClass constructor called.");
}
public void publicMethod() { // Public method
System.out.println("This is a public method.");
}
}
// package com.example.app
package com.example.app;
import com.example.model.PublicClass;
public class AccessPublic {
public static void main(String[] args) {
PublicClass obj = new PublicClass(); // Accessible
System.out.println(obj.publicField); // Accessible
obj.publicMethod(); // Accessible
}
}
2. protected
The protected
access modifier allows members to be accessed within:
- The same class.
- Classes in the same package.
- Subclasses (children classes) in any package.
It's primarily used when you want to allow subclasses to access a member, but prevent unrelated classes from doing so.
Visibility:
- Within the same class.
- Within the same package.
- By subclasses (even if in a different package).
Example:
// package com.example.model
package com.example.model;
public class Vehicle {
protected String type = "Generic Vehicle";
protected void startEngine() {
System.out.println("Engine started for " + type);
}
}
// package com.example.model (same package)
package com.example.model;
class Garage {
public void testVehicle() {
Vehicle car = new Vehicle();
car.startEngine(); // Accessible, same package
System.out.println(car.type); // Accessible, same package
}
}
// package com.example.app (different package, subclass)
package com.example.app;
import com.example.model.Vehicle; // Import needed for subclassing
public class Car extends Vehicle { // Car is a subclass of Vehicle
public Car() {
this.type = "Car"; // Accessible, as Car is a subclass
}
public void drive() {
startEngine(); // Accessible, as Car is a subclass
System.out.println(type + " is driving.");
}
public static void main(String[] args) {
Car myCar = new Car();
myCar.drive(); // Output: Engine started for Car, Car is driving.
}
}
// package com.example.app (different package, NOT a subclass)
package com.example.app;
// import com.example.model.Vehicle; // Uncomment if needed
public class Mechanic {
public static void main(String[] args) {
// Vehicle someVehicle = new Vehicle(); // Might need import
// someVehicle.startEngine(); // COMPILE ERROR: Cannot access protected member outside package unless subclass
// System.out.println(someVehicle.type); // COMPILE ERROR
}
}
3. default
(Package-Private)
When no access modifier is explicitly specified for a class, method, or field, it gets the default
access level. This is also known as "package-private" access.
default
members are accessible only within:
- The same class.
- Other classes within the same package.
They are not accessible by subclasses in different packages.
Visibility:
- Within the same class.
- Within the same package.
Example:
// package com.example.data
package com.example.data;
class UserProfile { // Default access class (only accessible within com.example.data)
String userName = "Guest"; // Default access field
void displayProfile() { // Default access method
System.out.println("User: " + userName);
}
}
// package com.example.data (same package)
package com.example.data;
public class ProfileManager {
public void showUserProfile() {
UserProfile user = new UserProfile(); // Accessible (same package)
System.out.println("Manager accessing: " + user.userName); // Accessible
user.displayProfile(); // Accessible
}
}
// package com.example.app (different package)
package com.example.app;
// import com.example.data.UserProfile; // COMPILE ERROR: UserProfile is not public in com.example.data; cannot be accessed from outside package
public class MainApp {
public static void main(String[] args) {
// UserProfile user = new UserProfile(); // COMPILE ERROR (UserProfile is default access)
// new com.example.data.UserProfile(); // COMPILE ERROR (even with fully qualified name)
// However, if ProfileManager (public) exposes UserProfile, it's fine for public members
// ProfileManager manager = new ProfileManager();
// manager.showUserProfile(); // This would work if ProfileManager were imported and public
}
}
4. private
The private
access modifier is the most restrictive. private
members (fields, methods, constructors) are accessible only from within the same class in which they are declared.
private
classes are not directly supported (except for nested classes).private
is fundamental for encapsulation, allowing classes to hide their internal state and implementation details from the outside world.
Visibility: Accessible only within the same class.
Example:
// package com.example.core
package com.example.core;
public class BankAccount {
private double balance; // Private field
public BankAccount(double initialBalance) { // Public constructor for external access
this.balance = initialBalance;
}
private void logTransaction(String message) { // Private method
System.out.println("Transaction Log: " + message);
}
public void deposit(double amount) { // Public method to interact with balance
if (amount > 0) {
this.balance += amount;
logTransaction("Deposited " + amount + ". New balance: " + balance); // Accessible within class
}
}
public double getBalance() { // Public getter
return balance;
}
}
// package com.example.app
package com.example.app;
import com.example.core.BankAccount;
public class BankingApp {
public static void main(String[] args) {
BankAccount myAccount = new BankAccount(1000.0);
myAccount.deposit(500.0); // Accessible
System.out.println("Current balance: " + myAccount.getBalance()); // Accessible
// myAccount.balance = 2000.0; // COMPILE ERROR: balance has private access
// myAccount.logTransaction("Unauthorized access"); // COMPILE ERROR: logTransaction has private access
}
}
Summary Table
Modifier | Same Class | Same Package | Subclass (different package) | Anywhere (different package, not subclass) |
---|---|---|---|---|
private | Yes | No | No | No |
default | Yes | Yes | No | No |
protected | Yes | Yes | Yes | No |
public | Yes | Yes | Yes | Yes |
Key Takeaways
- Encapsulation: Access modifiers are crucial for enforcing encapsulation. They allow you to hide the internal workings of a class, exposing only what is necessary, which makes code more maintainable and less prone to errors.
- API Design: When designing APIs, you typically expose
public
methods and classes that define the public contract of your component, while keeping internal state and helper methodsprivate
orprotected
. - Flexibility vs. Control: Each modifier offers a different balance between flexibility (how widely accessible a member is) and control (how much you restrict its usage).
- Class Level Access: Top-level classes can only be
public
ordefault
. Inner/nested classes can use all four access modifiers.