Encapsulation in Java
Learn to protect your data and control how it's accessed
💡 Think of encapsulation like a capsule that keeps medicine safe inside, or a backpack that protects your stuff! You can't just reach inside someone's backpack directly - you have to ask them to open it (getters) or put something in (setters). Encapsulation wraps data and methods together and controls access to keep things safe!
🔒 What is Encapsulation?
Encapsulation is the bundling of data (fields) and methods that operate on that data into a single unit (class), and restricting direct access to some of the object's components. It's like putting your valuables in a safe - only authorized people can access them through the proper methods!
// BAD EXAMPLE - No Encapsulation ❌class PersonBad { // Public fields - anyone can modify directly! public String name; public int age; public double salary;}class BadDemo { public static void main(String[] args) { PersonBad person = new PersonBad(); // Direct access - can set invalid values! person.age = -5; // ❌ Negative age?! person.salary = -1000; // ❌ Negative salary?! person.name = ""; // ❌ Empty name?! // No validation, no control - BAD! }}// GOOD EXAMPLE - With Encapsulation ✓class PersonGood { // Private fields - hidden from outside private String name; private int age; private double salary; // Constructor public PersonGood(String name, int age, double salary) { setName(name); // Use setters for validation setAge(age); setSalary(salary); } // Getter for name - controlled READ access public String getName() { return name; } // Setter for name - controlled WRITE access with validation public void setName(String name) { if (name != null && !name.trim().isEmpty()) { this.name = name; } else { System.out.println("Error: Name cannot be empty!"); } } // Getter for age public int getAge() { return age; } // Setter for age with validation public void setAge(int age) { if (age >= 0 && age <= 150) { this.age = age; } else { System.out.println("Error: Invalid age!"); } } // Getter for salary public double getSalary() { return salary; } // Setter for salary with validation public void setSalary(double salary) { if (salary >= 0) { this.salary = salary; } else { System.out.println("Error: Salary cannot be negative!"); } } public void displayInfo() { System.out.println("Name: " + name); System.out.println("Age: " + age); System.out.println("Salary: $" + salary); }}class GoodDemo { public static void main(String[] args) { PersonGood person = new PersonGood("Alice", 30, 75000); // Can't access fields directly // person.age = -5; // ❌ Compilation error! // Must use setters - validation happens automatically! person.setAge(-5); // ✓ Rejected! "Invalid age!" person.setSalary(-1000); // ✓ Rejected! "Salary cannot be negative!" // Valid values work fine person.setAge(31); person.setSalary(80000); // Read data using getters System.out.println("Name: " + person.getName()); System.out.println("Age: " + person.getAge()); person.displayInfo(); }}❓ Why Use Encapsulation?
- ✓Data Protection: Prevents unauthorized or invalid modifications
- ✓Flexibility: Change internal implementation without affecting users
- ✓Maintainability: Easier to modify and debug code
- ✓Control: Validate data before allowing changes
- ✓Hide Complexity: Users don't need to know internal details
🚦 Access Modifiers
private
Accessible only within the same class
Use for sensitive data that should be hidden
default (no modifier)
Accessible within the same package
Use for package-level access
protected
Accessible in same package and subclasses
Use when child classes need access
public
Accessible from anywhere
Use for methods that form the public API
class AccessDemo { // Private - only accessible within this class private String secret = "Top Secret!"; // Default (no modifier) - accessible within same package String packageData = "Package level data"; // Protected - accessible in same package and subclasses protected String familySecret = "Family only"; // Public - accessible from anywhere public String publicInfo = "Everyone can see this"; // Private method - only this class can call private void privateMethod() { System.out.println("This is private: " + secret); } // Public method - anyone can call public void publicMethod() { System.out.println("This is public: " + publicInfo); privateMethod(); // Can call private method from inside }}class TestAccess { public static void main(String[] args) { AccessDemo demo = new AccessDemo(); // Can access public System.out.println(demo.publicInfo); // ✓ Works demo.publicMethod(); // ✓ Works // Cannot access private // System.out.println(demo.secret); // ❌ Compilation error! // demo.privateMethod(); // ❌ Compilation error! // Can access default (if in same package) System.out.println(demo.packageData); // ✓ Works (same package) // Can access protected (if in same package or subclass) System.out.println(demo.familySecret); // ✓ Works (same package) }}🏦 Real-World Example: Bank Account
class BankAccount { // Private fields - hidden from outside private String accountNumber; private String ownerName; private double balance; private String pin; // Constructor public BankAccount(String accountNumber, String ownerName, String pin) { this.accountNumber = accountNumber; this.ownerName = ownerName; this.pin = pin; this.balance = 0.0; } // Public getter - controlled READ access public String getAccountNumber() { return accountNumber; } public String getOwnerName() { return ownerName; } // Balance can only be read, not set directly! public double getBalance() { return balance; } // NO setter for PIN - once set, cannot be changed this way! // (In real app, would have changePin method with verification) // Controlled methods to modify balance public void deposit(double amount) { if (amount > 0) { balance += amount; System.out.println("Deposited: $" + amount); System.out.println("New balance: $" + balance); } else { System.out.println("Error: Deposit amount must be positive!"); } } public boolean withdraw(double amount, String enteredPin) { // Verify PIN first! if (!verifyPin(enteredPin)) { System.out.println("Error: Invalid PIN!"); return false; } // Validate amount if (amount <= 0) { System.out.println("Error: Withdrawal amount must be positive!"); return false; } // Check sufficient funds if (amount > balance) { System.out.println("Error: Insufficient funds!"); System.out.println("Balance: $" + balance + ", Requested: $" + amount); return false; } // All checks passed - perform withdrawal balance -= amount; System.out.println("Withdrew: $" + amount); System.out.println("New balance: $" + balance); return true; } // Private helper method - only used internally private boolean verifyPin(String enteredPin) { return this.pin.equals(enteredPin); } public void displayAccountInfo(String enteredPin) { if (!verifyPin(enteredPin)) { System.out.println("Error: Invalid PIN! Cannot display info."); return; } System.out.println("=== Account Information ==="); System.out.println("Account: " + accountNumber); System.out.println("Owner: " + ownerName); System.out.println("Balance: $" + balance); }}class BankDemo { public static void main(String[] args) { BankAccount account = new BankAccount("ACC001", "Alice", "1234"); // Cannot access private fields directly // account.balance = 1000000; // ❌ Compilation error! // account.pin = "0000"; // ❌ Compilation error! // Must use public methods account.deposit(1000); account.deposit(500); // Try withdrawal with wrong PIN account.withdraw(200, "9999"); // ❌ Invalid PIN! // Try withdrawal with correct PIN account.withdraw(200, "1234"); // ✓ Success! // Try to withdraw more than balance account.withdraw(2000, "1234"); // ❌ Insufficient funds! // Display info with correct PIN account.displayAccountInfo("1234"); // This is encapsulation - data is protected and controlled! }}🎓 Student Example with Validation
class Student { // Private fields private String studentId; private String name; private int age; private double gpa; private String email; // Constructor with validation public Student(String studentId, String name, int age, String email) { this.studentId = studentId; setName(name); setAge(age); setEmail(email); this.gpa = 0.0; // Default GPA } // Getters - provide READ access public String getStudentId() { return studentId; } public String getName() { return name; } public int getAge() { return age; } public double getGpa() { return gpa; } public String getEmail() { return email; } // Setters with validation - provide controlled WRITE access public void setName(String name) { if (name != null && name.trim().length() >= 2) { this.name = name.trim(); } else { System.out.println("Error: Name must be at least 2 characters!"); } } public void setAge(int age) { if (age >= 16 && age <= 100) { this.age = age; } else { System.out.println("Error: Age must be between 16 and 100!"); } } public void setGpa(double gpa) { if (gpa >= 0.0 && gpa <= 4.0) { this.gpa = gpa; System.out.println("GPA updated to: " + this.gpa); } else { System.out.println("Error: GPA must be between 0.0 and 4.0!"); } } public void setEmail(String email) { // Simple email validation if (email != null && email.contains("@") && email.contains(".")) { this.email = email.toLowerCase(); } else { System.out.println("Error: Invalid email format!"); } } // Business logic method public String getAcademicStatus() { if (gpa >= 3.5) { return "Honor Student"; } else if (gpa >= 3.0) { return "Good Standing"; } else if (gpa >= 2.0) { return "Satisfactory"; } else { return "Academic Probation"; } } public void displayInfo() { System.out.println("=== Student Information ==="); System.out.println("ID: " + studentId); System.out.println("Name: " + name); System.out.println("Age: " + age); System.out.println("Email: " + email); System.out.println("GPA: " + gpa); System.out.println("Status: " + getAcademicStatus()); }}class StudentDemo { public static void main(String[] args) { Student student = new Student("S12345", "Bob Smith", 20, "bob@university.edu"); student.displayInfo(); System.out.println(); // Try setting invalid values - they will be rejected! student.setAge(-5); // ❌ Invalid age student.setAge(200); // ❌ Invalid age student.setGpa(5.0); // ❌ Invalid GPA student.setEmail("bademail"); // ❌ Invalid email System.out.println(); // Set valid values - they will be accepted student.setAge(21); // ✓ Valid student.setGpa(3.7); // ✓ Valid student.setEmail("bob.smith@university.edu"); // ✓ Valid System.out.println(); student.displayInfo(); // Encapsulation protects data integrity! }}📖 Read-Only Properties (No Setters)
class Product { // Read-only fields - set once in constructor, never changed private final String productId; private final String sku; private final String manufacturer; // Regular fields - can be changed private String name; private double price; private int stockQuantity; public Product(String productId, String sku, String manufacturer, String name, double price) { // Set read-only fields this.productId = productId; this.sku = sku; this.manufacturer = manufacturer; // Set regular fields this.name = name; this.price = price; this.stockQuantity = 0; } // Getters for read-only fields - NO SETTERS! public String getProductId() { return productId; } public String getSku() { return sku; } public String getManufacturer() { return manufacturer; } // Getters and setters for changeable fields public String getName() { return name; } public void setName(String name) { if (name != null && !name.trim().isEmpty()) { this.name = name; } } public double getPrice() { return price; } public void setPrice(double price) { if (price > 0) { this.price = price; System.out.println("Price updated to: $" + price); } } public int getStockQuantity() { return stockQuantity; } // Controlled method to modify stock public void addStock(int quantity) { if (quantity > 0) { stockQuantity += quantity; System.out.println("Added " + quantity + " units. Stock: " + stockQuantity); } } public void removeStock(int quantity) { if (quantity > 0 && quantity <= stockQuantity) { stockQuantity -= quantity; System.out.println("Removed " + quantity + " units. Stock: " + stockQuantity); } else { System.out.println("Error: Invalid quantity!"); } } public void displayInfo() { System.out.println("Product ID: " + productId + " (Read-only)"); System.out.println("SKU: " + sku + " (Read-only)"); System.out.println("Manufacturer: " + manufacturer + " (Read-only)"); System.out.println("Name: " + name); System.out.println("Price: $" + price); System.out.println("Stock: " + stockQuantity); }}class ProductDemo { public static void main(String[] args) { Product laptop = new Product("P001", "LAP-001", "TechCorp", "Gaming Laptop", 1299.99); laptop.displayInfo(); System.out.println(); // Can change name and price laptop.setName("Pro Gaming Laptop"); laptop.setPrice(1199.99); // Can modify stock through controlled methods laptop.addStock(50); laptop.removeStock(5); System.out.println(); // Cannot change read-only fields // laptop.productId = "P999"; // ❌ Compilation error - private final! // No setProductId() method exists! laptop.displayInfo(); // Read-only properties ensure data consistency! }}🔐 Immutable Class (Cannot Change After Creation)
// Immutable class - once created, cannot be modifiedfinal class Point { // final class - cannot be extended // All fields are private and final private final int x; private final int y; // Constructor - only way to set values public Point(int x, int y) { this.x = x; this.y = y; } // Only getters, NO setters! public int getX() { return x; } public int getY() { return y; } // If you need to "change" values, return NEW object public Point moveTo(int newX, int newY) { return new Point(newX, newY); // New object, original unchanged! } public Point moveBy(int deltaX, int deltaY) { return new Point(x + deltaX, y + deltaY); // New object! } @Override public String toString() { return "Point(" + x + ", " + y + ")"; }}// Another immutable class examplefinal class Money { private final double amount; private final String currency; public Money(double amount, String currency) { this.amount = amount; this.currency = currency; } public double getAmount() { return amount; } public String getCurrency() { return currency; } // Operations return NEW objects public Money add(Money other) { if (!this.currency.equals(other.currency)) { throw new IllegalArgumentException("Currency mismatch!"); } return new Money(this.amount + other.amount, this.currency); } public Money subtract(Money other) { if (!this.currency.equals(other.currency)) { throw new IllegalArgumentException("Currency mismatch!"); } return new Money(this.amount - other.amount, this.currency); } public Money multiply(double factor) { return new Money(this.amount * factor, this.currency); } @Override public String toString() { return currency + " " + amount; }}class ImmutableDemo { public static void main(String[] args) { // Create immutable point Point p1 = new Point(10, 20); System.out.println("p1: " + p1); // "Move" the point - actually creates new point! Point p2 = p1.moveTo(30, 40); System.out.println("p1: " + p1); // Original unchanged! System.out.println("p2: " + p2); // New point created Point p3 = p1.moveBy(5, 5); System.out.println("p1: " + p1); // Still unchanged! System.out.println("p3: " + p3); System.out.println(); // Create immutable money Money wallet1 = new Money(100.00, "USD"); Money wallet2 = new Money(50.00, "USD"); System.out.println("Wallet 1: " + wallet1); System.out.println("Wallet 2: " + wallet2); // Add money - creates new object! Money total = wallet1.add(wallet2); System.out.println("Total: " + total); System.out.println("Wallet 1 (unchanged): " + wallet1); System.out.println("Wallet 2 (unchanged): " + wallet2); // Immutability = thread-safe, predictable, no side effects! }}🔑 Key Concepts
Private Fields
Data hidden from outside access
private String password;
Getter Methods
Public methods to read private data
public String getName() { return name; }
Setter Methods
Public methods to modify private data (with validation)
public void setAge(int age) { if(age > 0) this.age = age; }
Data Hiding
Hide implementation details from users
Users call methods, don't access fields directly
✨ Best Practices
- ✓Always make fields private unless there's a good reason not to
- ✓Provide public getter/setter methods only when needed
- ✓Validate data in setter methods before assigning
- ✓Use meaningful names: getName(), setAge(), not get() or set()
- ✓Make classes immutable when possible (final fields, no setters)
- ✓Follow JavaBeans naming convention: getXxx() and setXxx()
💼 Interview Tips
- •Encapsulation is one of the four pillars of OOP
- •It provides data hiding and abstraction
- •Getters return field values, setters modify them
- •Validation in setters prevents invalid states
- •Private fields + public methods = encapsulation
- •Encapsulation makes code more maintainable and flexible