Encapsulation
Dashboard
Topic 13/30
Home › Encapsulation

🔒 Encapsulation

The practice of wrapping data (variables) and code acting on the data (methods) together as a single unit, keeping the fields safe from outside interference.

1. What is Encapsulation?

Encapsulation is one of the four fundamental OOP concepts (along with Inheritance, Polymorphism, and Abstraction). It is the mechanism of wrapping data (variables) and code acting on the data (methods) together as a single unit. In encapsulation, the variables of a class will be hidden from other classes and can be accessed only through the methods of their current class. Therefore, it is also known as data hiding.

To achieve encapsulation in Java: 1. Declare the variables of a class as private. 2. Provide public setter and getter methods to modify and view the variables' values.

Think of an ATM machine. When you use an ATM, you interact with the screen, buttons, and cash dispenser. You cannot directly access the internal cash box or the core database holding your account balance. The ATM encapsulates the money and the logic of withdrawing it, providing a safe, validated public interface (the buttons and screen) while keeping the sensitive parts private and secure. Similarly, in a Java class, we keep our fields (the cash box) private and provide methods (the ATM buttons) to interact with them safely.

2. Private Fields & Public Getters/Setters

By marking fields as private, you ensure that no outside class can directly read or modify the data. Instead, you provide public getter and setter methods. Without private fields, anyone could assign invalid data to your variables, bypassing any logic you might want to enforce.

Warning: Public fields violate encapsulation. If `public int age;` is used, anyone can do `person.age = -500;`. Private fields force users to use `person.setAge(-500);` where you can reject the invalid value.

3. Validation in Setters

The primary reason for using getters and setters is to gain control over the data. By forcing data changes to go through a method, you can validate the input before changing the actual field. This prevents your objects from ending up in an invalid state.

Employee.javaJava
public class Employee {
    private String name;
    private int age;
    private double salary;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    // Validation in setter
    public void setAge(int age) {
        if (age >= 18 && age <= 65) {
            this.age = age;
        } else {
            System.out.println("Invalid age. Must be between 18 and 65.");
        }
    }

    public double getSalary() {
        return salary;
    }

    // Validation in setter
    public void setSalary(double salary) {
        if (salary > 0) {
            this.salary = salary;
        } else {
            System.out.println("Salary must be positive.");
        }
    }

    @Override
    public String toString() {
        return "Employee{name='" + name + "', age=" + age + ", salary=" + salary + "}";
    }
}

4. JavaBeans Convention

A JavaBean is a standard convention in Java for creating classes. To be a JavaBean, a class must:

  • Have a public no-arg (default) constructor.
  • Make fields private.
  • Provide standard public getter and setter methods using a specific naming convention: getFieldName() and setFieldName().
  • For boolean fields, the getter should be named isFieldName() instead of getFieldName().
  • Implement the java.io.Serializable interface (often).

5. Benefits of Encapsulation

BenefitDescription
Data HidingInternal state is hidden and protected from unauthorized direct access.
Increased FlexibilityYou can make variables read-only (only getter) or write-only (only setter).
ReusabilityEncapsulated classes are modular and can be reused easily across different projects.
Testing CodeEncapsulated code is easier to test for unit testing because dependencies and state are controlled.
MaintainabilityYou can change the internal implementation (like changing a list to a map) without breaking the code of users who call your getters/setters.

6. Immutable Classes

An immutable class is a class whose instances cannot be modified once they are created. This is the ultimate form of encapsulation. To create an immutable class:

  1. Declare the class as final so it cannot be subclassed (preventing overriding methods to change behavior).
  2. Make all fields private and final.
  3. Do not provide any setter methods.
  4. Initialize all fields via a constructor or a static factory method.
  5. If the class has mutable object fields (like arrays or dates), make defensive copies in the constructor and getters.
ImmutableStudent.javaJava
// 1. Class is final
public final class ImmutableStudent {
    // 2. Fields are private and final
    private final String id;
    private final String name;

    // 3. Private constructor initialized all fields
    private ImmutableStudent(String id, String name) {
        this.id = id;
        this.name = name;
    }

    // Static factory method
    public static ImmutableStudent create(String id, String name) {
        return new ImmutableStudent(id, name);
    }

    // 4. Only getters, no setters
    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Student[" + id + ": " + name + "]";
    }
}

7. Why String is Immutable in Java

The String class in Java is a perfect example of an immutable class. It is immutable for several important reasons:

  • Security: Strings are widely used as parameters for network connections, database URLs, and file paths. If String were mutable, a malicious user could change the string reference after security checks are performed.
  • Thread Safety: Because they cannot be modified, Strings are inherently thread-safe and can be shared among multiple threads without synchronization.
  • String Pool: Immutability allows Java to safely cache String literals in the String Constant Pool, saving massive amounts of memory.
  • Caching Hashcodes: The hashcode of a String is cached when it is first calculated. This makes it very fast to use as a key in HashMaps.

8. Advanced Encapsulation Example

BankAccountSecure.javaJava
import java.util.ArrayList;
import java.util.List;

public class BankAccountSecure {
    private final String accountNumber;
    private double balance;
    private final List<String> transactionHistory;

    public BankAccountSecure(String accountNumber, double initialBalance) {
        this.accountNumber = accountNumber;
        this.transactionHistory = new ArrayList<>();
        if (initialBalance >= 0) {
            this.balance = initialBalance;
            transactionHistory.add("Initial deposit: $" + initialBalance);
        } else {
            this.balance = 0;
            transactionHistory.add("Account opened with $0 balance");
        }
    }

    public String getAccountNumber() {
        return accountNumber;
    }

    public double getBalance() {
        return balance;
    }

    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            transactionHistory.add("Deposited: $" + amount);
        }
    }

    public boolean withdraw(double amount) {
        if (amount > 0 && balance >= amount) {
            balance -= amount;
            transactionHistory.add("Withdrew: $" + amount);
            return true;
        }
        transactionHistory.add("Failed withdrawal attempt: $" + amount);
        return false;
    }

    // Return a defensive copy to protect internal list
    public List<String> getTransactionHistory() {
        return new ArrayList<>(transactionHistory);
    }
}
Main.javaJava
public class Main {
    public static void main(String[] args) {
        // 1. Employee Demo
        Employee emp = new Employee();
        emp.setName("Alice");
        emp.setAge(15); // Invalid
        emp.setAge(30); // Valid
        emp.setSalary(50000);
        System.out.println(emp);

        // 2. Immutable Demo
        ImmutableStudent student = ImmutableStudent.create("S101", "Bob");
        System.out.println(student);

        // 3. Bank Account Demo
        BankAccountSecure account = new BankAccountSecure("ACC-123", 100);
        account.deposit(50);
        account.withdraw(200); // Fails
        account.withdraw(40);

        System.out.println("Balance: $" + account.getBalance());
        System.out.println("History: " + account.getTransactionHistory());
    }
}
Output
Invalid age. Must be between 18 and 65. Employee{name='Alice', age=30, salary=50000.0} Student[S101: Bob] Balance: $110.0 History: [Initial deposit: $100.0, Deposited: $50.0, Failed withdrawal attempt: $200.0, Withdrew: $40.0]