1. The Hook (The "Byte-Sized" Intro)
In a Nutshell: Choosing the right access modifier is critical for API design, maintainability, and security. Use private for implementation details, default for package-internal helpers, protected for extension points, and public for stable APIs. Follow the Principle of Least Privilegeβstart restrictive, widen only when needed.
Think of Facebook privacy settings: "Only Me" (private), "Friends" (default), "Friends of Friends" (protected), "Public" (public). You control who sees what!
2. Conceptual Clarity (The "Simple" Tier)
π‘ The Analogy: The Hotel Access
- Private: Your room (only you)
- Default: Hotel floor (guests on your floor)
- Protected: Hotel + sister hotels (guests in the hotel chain)
- Public: Hotel lobby (anyone)
3. Technical Mastery (The "Deep Dive")
Complete Comparison Table
| Feature | private | default | protected | public |
|---|---|---|---|---|
| Visibility | Class only | Package | Package + Subclasses | Everywhere |
| Keyword | private | (none) | protected | public |
| Use Case | Implementation | Internal API | Extension points | Public API |
| Can Change? | Yes (safe) | Risky | Very risky | Never (breaking) |
| Top-level Class? | β No | β Yes | β No | β Yes |
Decision Matrix
4. Interactive & Applied Code
// Good Example: Proper Access Levels
public class BankAccount { // public: API class
private double balance; // private: implementation detail
private String accountNumber; // private: sensitive data
BankAccount(String number) { // default: package-only constructor
this.accountNumber = number;
this.balance = 0;
}
public void deposit(double amount) { // public: API method
if (amount > 0) {
balance += amount;
logTransaction("deposit", amount); // private method
}
}
public double getBalance() { // public: API method
return balance;
}
protected void validateTransaction(double amount) { // protected: for subclasses
if (amount < 0) throw new IllegalArgumentException("Negative amount");
}
private void logTransaction(String type, double amount) { // private: internal
System.out.println(type + ": $" + amount);
}
}
class SavingsAccount extends BankAccount {
SavingsAccount(String number) {
super(number);
}
public void addInterest(double rate) {
double interest = getBalance() * rate;
validateTransaction(interest); // β
Can access protected method
deposit(interest);
}
}5. The Comparison & Decision Layer
When to Use Each
| Scenario | Modifier |
|---|---|
| Fields | Almost always private |
| Getters/Setters | public (if needed) |
| Utility methods (package) | default |
| Template methods | protected |
| API methods | public |
| Constructors (framework use) | Often protected or default |
Best Practices
- Start with
privateβ widen only if needed - Never make fields
public(use getters/setters) - Use
defaultfor package-internal utilities - Use
protectedfor extension points (Template Method pattern) - Mark public APIs clearly (documentation, @API annotation)
6. The "Interview Corner" (The Edge)
The "Killer" Interview Question: "Why should you never make instance variables public?" Answer:
- Breaks encapsulation: External code directly accesses internals
- No validation: Can't enforce invariants (e.g., age > 0)
- Inflexible: Can't change implementation without breaking clients
- Thread-safety: Can't synchronize access
Always use private fields + public getters/setters, even if "simple"βyou might need validation/logging later!
Real-World Example
// β BAD
public class User {
public String email; // Anyone can set invalid email!
}
// β
GOOD
public class User {
private String email;
public void setEmail(String email) {
if (!email.contains("@")) {
throw new IllegalArgumentException("Invalid email");
}
this.email = email;
}
}Pro-Tip: Follow Effective Java guidelines:
- Item 15: Minimize the accessibility of classes and members
- Item 16: In public classes, use accessor methods, not public fields
Summary: Private by default, public by necessity!