Encapsulation in Java

Encapsulation is one of the four fundamental principles of object-oriented programming (OOP) and refers to the technique of hiding the implementation details of an object from its external users. In Java, encapsulation is achieved by declaring the instance variables of a class as private, which means that they can only be accessed within the same class.

To provide access to these private instance variables, Java provides public methods called getters and setters, which are used to retrieve and modify the values of the private variables, respectively. This way, the external users of the object can interact with it only through these public methods, without being able to access or modify its internal state directly.

Encapsulation has several benefits, such as:

  • Enhancing the security of an object's data by preventing unauthorized access.
  • Allowing the implementation details of an object to be changed without affecting its external users.
  • Simplifying the code by reducing the number of dependencies between different parts of the program.
  • Providing a clear and well-defined interface for interacting with an object, which improves the readability and maintainability of the code.
  • Encapsulation is often referred to as the "data hiding" principle because it involves hiding the internal state of an object from the outside world.
  • By using encapsulation, you can create more robust and reliable code. Because the internal state of an object is hidden, you can change the implementation of the class without worrying about breaking other parts of the code that depend on it.
  • Encapsulation is closely related to the concept of abstraction, which involves hiding the implementation details of a system from its users. Together, abstraction and encapsulation provide a powerful way to manage complexity in large software projects.
  • Encapsulation is not just about hiding data; it also involves hiding behavior. In other words, you can use encapsulation to hide the methods that are used to implement the behavior of an object. This can be useful when you want to prevent external code from directly modifying the behavior of an object.
  • Encapsulation is not an all-or-nothing proposition. You can choose to encapsulate some parts of a class but not others, depending on your specific needs. For example, you might choose to encapsulate the data members of a class but leave the methods public, or vice versa.
  • Encapsulation is not unique to Java; it is a fundamental concept in all object-oriented programming languages. However, Java's strict access modifiers (private, protected, and public) make it particularly easy to implement encapsulation.
Example 1:

public class Person {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

In this example, we have a 'Person' class that has two private instance variables: 'name' and age. We also have two public methods, 'getName()' and 'getAge()', that allow external code to retrieve the values of these instance variables. We also have two public methods, 'setName()' and 'setAge()', that allow external code to modify the values of these instance variables.

Because the instance variables are declared as private, they cannot be accessed directly by external code. Instead, external code must use the public getters and setters to interact with the 'Person' object. This provides a layer of abstraction that protects the internal state of the object from external manipulation.

Here's an example of how this 'Person' class could be used in external code:

public class Main {
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("John Smith");
        person.setAge(30);
        System.out.println(person.getName() + " is " + person.getAge() + " years old.");
    }
}

In this example, we create a new 'Person' object and use the setters to set its 'name' and 'age' instance variables. We then use the getters to retrieve these values and print them to the console. Because the instance variables are encapsulated, the external code cannot modify them directly and must use the public getters and setters to interact with the object.

Example 2:

public class BankAccount {
    private String accountNumber;
    private double balance;
    private String password;

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

    public String getAccountNumber() {
        return accountNumber;
    }

    public double getBalance() {
        return balance;
    }

    public void deposit(double amount) {
        balance += amount;
    }

    public void withdraw(double amount, String password) {
        if (password.equals(this.password)) {
            if (amount <= balance) {
                balance -= amount;
            } else {
                throw new IllegalArgumentException("Insufficient funds");
            }
        } else {
            throw new IllegalArgumentException("Invalid password");
        }
    }
}

In this example, we have a 'BankAccount' class that represents a simple bank account. The class has three private instance variables: 'accountNumber', 'balance', and 'password'. We also have a constructor that allows us to set these instance variables when we create a new 'BankAccount' object.

The class has three public methods: 'getAccountNumber()', 'getBalance()', and 'deposit()'. These methods allow external code to retrieve the 'accountNumber' and 'balance' instance variables and to deposit money into the account.

The class also has a private method called 'withdraw()'. This method allows a user to withdraw money from the account, but only if they provide the correct password. If the password is incorrect, the method throws an 'IllegalArgumentException'. If the password is correct but the account balance is too low, the method also throws an 'IllegalArgumentException'.

In this example, the use of encapsulation is particularly important because we want to ensure that the 'password' instance variable is protected from external manipulation. By making the 'password' variable private and only allowing access to it through the 'withdraw()' method, we ensure that only authorized users can withdraw money from the account. This is a crucial security feature that helps to protect the user's money from theft or fraud.

Here's an example of how this 'BankAccount' class could be used in external code:

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

        System.out.println("Account number: " + account.getAccountNumber());
        System.out.println("Initial balance: " + account.getBalance());

        account.deposit(500.0);
        System.out.println("New balance after deposit: " + account.getBalance());

        try {
            account.withdraw(200.0, "wrongpassword");
        } catch (IllegalArgumentException e) {
            System.out.println("Failed to withdraw: " + e.getMessage());
        }

        try {
            account.withdraw(2000.0, "mypassword");
        } catch (IllegalArgumentException e) {
            System.out.println("Failed to withdraw: " + e.getMessage());
        }

        account.withdraw(200.0, "mypassword");
        System.out.println("New balance after withdrawal: " + account.getBalance());
    }
}

In this example, we create a new 'BankAccount' object and use the getters and deposit method to interact with the object. We also use the 'withdraw()' method to withdraw money from the account, but only if we provide the correct password. If the password is incorrect, we catch the 'IllegalArgumentException' that is thrown and print an error message. If the password is correct but the withdrawal amount is too high, we also catch the 'IllegalArgumentException' and print an error message.

Example 3:

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

    public Employee(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public double getSalary() {
        return salary;
    }

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

    public void setAge(int age) {
        this.age = age;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}

In this example, we have an 'Employee' class that represents an employee in a company. The class has three private instance variables: 'name', 'age', and 'salary'. We also have a constructor that allows us to set these instance variables when we create a new 'Employee' object.

The class has six public methods: 'getName()', 'getAge()', 'getSalary()', 'setName()', 'setAge()', and 'setSalary()'. The getter methods allow external code to retrieve the 'name', 'age', and 'salary' instance variables, while the setter methods allow external code to modify these variables.

Encapsulation is important in this example because we want to ensure that the 'name', 'age', and 'salary' instance variables are protected from external manipulation. By making these variables private and only allowing access to them through the getter and setter methods, we ensure that external code cannot modify the variables directly. This helps to maintain data integrity and prevent bugs caused by unexpected changes to the variables.

Here's an example of how this 'Employee' class could be used in external code:

public class Main {
    public static void main(String[] args) {
        Employee employee = new Employee("John Smith", 30, 50000.0);

        System.out.println("Employee name: " + employee.getName());
        System.out.println("Employee age: " + employee.getAge());
        System.out.println("Employee salary: " + employee.getSalary());

        employee.setAge(31);
        employee.setSalary(55000.0);
        System.out.println("Employee new age: " + employee.getAge());
        System.out.println("Employee new salary: " + employee.getSalary());
    }
}

In this example, we create a new 'Employee' object and use the getter methods to retrieve the employee's name, age, and salary. We also use the setter method to modify the employee's age and salary. By using the getter and setter methods instead of modifying the variables directly, we ensure that the data remains consistent and the program behaves as expected.
Next Post Previous Post
No Comment
Add Comment
comment url