OOP

Encapsulation in Java Tutorial with Code Examples

Encapsulation is one of the four fundamental concepts of object-oriented programming (OOP), alongside inheritance, polymorphism, and abstraction. Encapsulation in Java refers to the bundling of data (fields) and the methods that operate on the data (methods) into a single unit, typically a class. It also helps in protecting the internal state of an object from unintended modifications by restricting access to its fields and providing controlled access through methods.

In this tutorial, we'll dive into encapsulation, why it’s important, and how to implement it in Java using practical examples.

Table of Contents:

1. What is Encapsulation?

Encapsulation is the practice of wrapping data (variables) and methods that manipulate the data into a single unit, usually a class. It provides a way to control access to the data by making fields private and exposing getter and setter methods to modify and access these fields.

This hides the internal implementation details and protects the object's state from unauthorized access or modification.

2. Why Use Encapsulation?

Encapsulation offers several key benefits:

Data hiding: The internal state of an object is hidden from the outside world, preventing external interference.
Control over data: By using getter and setter methods, you can add validation logic before updating fields.
Modularity: Changes to the internal implementation can be made without affecting other parts of the code that depend on the class.
Increased security: Encapsulation ensures that the sensitive data is only accessible and modifiable through controlled methods.

3. How to Achieve Encapsulation in Java

Encapsulation in Java is achieved by:

Declaring the fields (instance variables) of a class as private.
Providing public getter and setter methods to access and update the private fields.

4. Access Modifiers and Encapsulation

Java uses access modifiers to enforce encapsulation. The access modifiers that control the visibility of fields and methods are:

private: Visible only within the class.
public: Visible everywhere.
protected: Visible within the package and subclasses.
(default): Visible within the package.
For encapsulation, fields are usually marked as private, and getter and setter methods are marked as public to control access.

5. Code Example of Encapsulation in Java

Example 1: Encapsulation with Getter and Setter Methods

class Person {
    // Private fields (Encapsulation: Hiding data)
    private String name;
    private int age;

    // Getter method for name
    public String getName() {
        return name;
    }

    // Setter method for name
    public void setName(String name) {
        this.name = name;
    }

    // Getter method for age
    public int getAge() {
        return age;
    }

    // Setter method for age (with validation)
    public void setAge(int age) {
        if (age > 0) {
            this.age = age;
        } else {
            System.out.println("Age must be a positive number.");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        // Creating an instance of Person
        Person person = new Person();

        // Setting values using setter methods
        person.setName("John");
        person.setAge(25);

        // Getting values using getter methods
        System.out.println("Name: " + person.getName());  // Output: Name: John
        System.out.println("Age: " + person.getAge());    // Output: Age: 25

        // Attempting to set an invalid age
        person.setAge(-5);  // Output: Age must be a positive number.
    }
}

Explanation:

The Person class encapsulates the name and age fields by marking them as private.
The setAge method includes validation logic to ensure the age is always positive.
The getter and setter methods allow controlled access to the private fields.

6. Getter and Setter Methods

Getter and setter methods are the standard way to implement encapsulation in Java. They provide controlled access to private fields and allow adding validation, logging, or other logic while getting or setting values.

Example 2: Getter and Setter with Additional Logic

class Employee {
    private String id;
    private double salary;

    // Getter for id
    public String getId() {
        return id;
    }

    // Setter for id with validation
    public void setId(String id) {
        if (id != null && !id.isEmpty()) {
            this.id = id;
        } else {
            System.out.println("ID cannot be null or empty.");
        }
    }

    // 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("Salary cannot be negative.");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Employee employee = new Employee();

        // Setting valid values
        employee.setId("EMP001");
        employee.setSalary(5000.0);

        // Getting the values
        System.out.println("Employee ID: " + employee.getId());  // Output: Employee ID: EMP001
        System.out.println("Employee Salary: " + employee.getSalary());  // Output: Employee Salary: 5000.0

        // Setting invalid values
        employee.setId("");
        employee.setSalary(-1000.0);
    }
}

Explanation:

The setId and setSalary methods include validation to ensure that invalid values (empty strings or negative numbers) are not allowed.
The fields id and salary remain private, preserving encapsulation.

7. Encapsulation and Data Hiding

Encapsulation helps in data hiding, which means restricting direct access to the internal state of an object and exposing only necessary parts of the object to the outside world.

By keeping fields private and providing controlled access via public methods, you can hide the implementation details and expose only what is necessary.

Example 3: Data Hiding

class BankAccount {
    // Private fields to hide sensitive data
    private String accountNumber;
    private double balance;

    public BankAccount(String accountNumber, double initialBalance) {
        this.accountNumber = accountNumber;
        this.balance = initialBalance;
    }

    // Getter for balance (data hiding)
    public double getBalance() {
        return balance;
    }

    // Method to deposit money
    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }

    // Method to withdraw money with validation
    public void withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
        } else {
            System.out.println("Insufficient funds or invalid amount.");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        BankAccount account = new BankAccount("123456789", 1000.0);

        // Deposit and withdraw operations
        account.deposit(500.0);
        account.withdraw(200.0);

        // Getting the account balance
        System.out.println("Account Balance: " + account.getBalance());  // Output: Account Balance: 1300.0
    }
}

Explanation:

The accountNumber and balance fields are private and not directly accessible, ensuring data hiding.
Methods like deposit() and withdraw() allow controlled access to the account's balance, ensuring that the internal state is modified only through well-defined operations.

8. Advantages of Encapsulation

Data Protection: Sensitive data is protected from outside modification by making fields private.
Improved Maintainability: Changes to the internal implementation do not affect external code, making the system more maintainable.
Enhanced Flexibility: The internal implementation can evolve without affecting how the class is used.
Reusability: Encapsulated code can be reused across different applications.
Loose Coupling: By hiding internal details, encapsulation encourages loose coupling between objects.

9. Real-World Example of Encapsulation

Consider an Order system where the Order class encapsulates the order details, and the user can only modify certain parts of the order through specific methods.

Example 4: Real-World Example

class Order {
    // Private fields
    private int orderId;
    private String product;
    private int quantity;

    // Constructor
    public Order(int orderId, String product, int quantity) {
        this.orderId = orderId;
        this.product = product;
        this.quantity = quantity;
    }

    // Getter for orderId (no setter, as orderId should not change)
    public int getOrderId() {
        return orderId;
    }

    // Getter and Setter for product
    public String getProduct() {
        return product;
    }

    public void setProduct(String product) {
        if (product != null && !product.isEmpty()) {
            this.product = product;
        } else {
            System.out.println("Invalid product name.");
        }
    }

    // Getter and Setter for quantity
    public int getQuantity() {
        return quantity;
    }

    public void setQuantity(int quantity) {
        if (quantity > 0) {
            this.quantity = quantity;
        } else {
            System.out.println("Quantity must be greater than zero.");
        }
    }

    // Method to display order details
    public void displayOrderDetails() {
        System.out.println("Order ID: " + orderId);
        System.out.println("Product: " + product);
        System.out.println("Quantity: " + quantity);
    }
}

public class Main {
    public static void main(String[] args) {
        // Creating an Order object
        Order order = new Order(101, "Laptop", 2);

        // Displaying initial order details
        order.displayOrderDetails();

        // Updating the order
        order.setProduct("Smartphone");
        order.setQuantity(3);

        // Displaying updated order details
        order.displayOrderDetails();
    }
}

Explanation:

The Order class encapsulates the order details such as orderId, product, and quantity.
The order ID cannot be changed after initialization, while the product and quantity can be updated using setter methods with validation.

10. Best Practices for Using Encapsulation

Make fields private: Always make fields private to hide the internal state of an object.
Use getter and setter methods: Provide public getter and setter methods to control access to the private fields.
Add validation in setters: Ensure that the setter methods contain validation logic to avoid incorrect or invalid data.
Expose only what is necessary: Avoid exposing fields or methods that are not required by external classes. This reduces coupling and increases flexibility.

11. Conclusion

Encapsulation is an essential concept in Java that helps to protect an object's internal state by exposing controlled access through getter and setter methods.

It improves maintainability, flexibility, and security in software development by hiding the implementation details and providing a clear interface for interacting with objects.

By following the best practices of encapsulation, you can create more modular, maintainable, and secure code in Java, ensuring that your classes are well-encapsulated and adhere to the principles of data hiding and abstraction.

Related posts

Java Hidden Classes

Java sealed classes

Java Static Classes