Encapsulation in Java Classes

Hello! In this lesson, we're revisiting Encapsulation, Private Attributes, and Private Methods in Object-Oriented Programming (OOP). Think of encapsulation as an invisible fence that safeguards your garden (the class) from outside interference, keeping data and methods secure inside. Within this garden, certain elements (Private Attributes and Methods) are only accessible to the gardener, providing an extra layer of protection and ensuring data integrity in your classes!

Encapsulation Explained

Encapsulation is a fundamental concept in Object-Oriented Programming (OOP) that involves bundling data (attributes) and methods (functions) that operate on the data into a single unit, called a class. This concept also limits direct access to some of an object's components, preventing accidental interference and misuse of the data. For instance, in a multiplayer game, you might create a Player class that encapsulates data (health, armor, stamina) and methods (receiveDamage, shieldHit, restoreHealth).

class Player {
    private int health;
    private int armor;
    private int stamina;

    public Player(int health, int armor, int stamina) {
        this.health = health;
        this.armor = armor;
        this.stamina = stamina;
    }

    public void receiveDamage(int damage) {
        health -= damage;  // Reduce health
    }
    
    public void shieldHit(int armorDamage) {
        armor -= armorDamage;  // Decrease armor
    }

    public void restoreHealth(int healthIncrease) {
        health += healthIncrease;  // Restore health
    }

    public void displayStatus() {
        System.out.println("Health: " + health + ", Armor: " + armor + ", Stamina: " + stamina);
    }
}

public class Solution {
    public static void main(String[] args) {
        Player player = new Player(100, 50, 77);
        player.displayStatus();  // Outputs: Health: 100, Armor: 50, Stamina: 77
    }
}

In Java, player is an instance of the Player class on which you can call methods.

Private Attributes

In Java, the private keyword is used to designate attributes and methods as accessible only within their own class. This ensures the integrity of an object by restricting direct external access and interference.

Private Attributes, such as balance in a BankAccount class, ensure the integrity and security of data by restricting modifications to the methods within the class and disallowing direct external access.

class BankAccount {
    private double balance; // Declare a private field

    // Constructor to initialize the balance
    public BankAccount(double balance) {
        this.balance = balance; // Initialize the private attribute
    }

    // Method to deposit money into the account
    public void deposit(double amount) {
        balance += amount; // Deposit money
    }

    // Getter for balance
    public double getBalance() {
        return balance; // A public method to access balance
    }

    // Optional setter for balance with validation
    public void setBalance(double balance) {
        if (balance >= 0) {
            this.balance = balance;
        } else {
            System.out.println("Balance cannot be negative.");
        }
    }
}

public class Solution {
    public static void main(String[] args) {
        BankAccount bankAccount = new BankAccount(100); // Initialize account with balance of 100
        bankAccount.setBalance(200); // Works: sets balance to 200
        System.out.println(bankAccount.getBalance()); // Works: logs 200
        bankAccount.setBalance(-50); // Logs: "Balance cannot be negative."
    }
}
Private Methods

Like private attributes, private methods are accessible only within their class. They are useful for hiding complex implementation details or helper functions from outside access. Here's an example:

class BankAccount {
    private double balance; // Declare a private field

    public BankAccount(double balance) {
        this.balance = balance; // Initialize the private attribute
    }

    // Private method to calculate interest
    private void addInterest(double interestRate) {
        balance += balance * interestRate; // Calculation of interest
    }

    // Public method calling the private method
    public void addYearlyInterest() {
        addInterest(0.02); // Adds 2% interest
    }

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

public class Solution {
    public static void main(String[] args) {
        BankAccount bankAccount = new BankAccount(100); // Initialize account with balance of 100
        bankAccount.addYearlyInterest(); // Works, calling a public method
        System.out.println(bankAccount.getBalance()); // Logs the updated balance
        // bankAccount.addInterest(0.1); // Error: can't call a private method from outside
    }
}

Here, addYearlyInterest() is a public method that calls the private method addInterest().

Getters

In Java, getters provide a method to access private attributes indirectly while maintaining control over how values are retrieved, adhering to the encapsulation principle. Here's how you can define a getter method for a private attribute:

class BankAccount {
    private double balance; // Declare a private field

    // Constructor to initialize the balance
    public BankAccount(double balance) {
        this.balance = balance; // Initialize the private attribute
    }

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

    // Method to deposit money into the account
    public void deposit(double amount) {
        balance += amount; // Deposit money
    }
}

public class Solution {
    public static void main(String[] args) {
        BankAccount bankAccount = new BankAccount(100); // Initialize account with balance of 100
        System.out.println(bankAccount.getBalance()); // Works: logs 100
    }
}

In this example, the getBalance method provides a controlled way to access the private balance attribute.

Setters

Java setters allow the modification of the value of a private attribute through a controlled interface. Here’s how you can define a setter method for a private attribute:

class BankAccount {
    private double balance; // Declare a private field

    // Constructor to initialize the balance
    public BankAccount(double balance) {
        this.balance = balance; // Initialize the private attribute
    }

    // Setter for balance with validation
    public void setBalance(double balance) {
        if (balance >= 0) {
            this.balance = balance;
        } else {
            System.out.println("Balance cannot be negative.");
        }
    }

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

    // Method to deposit money into the account
    public void deposit(double amount) {
        this.balance += amount; // Deposit money
    }
}

public class Solution {
    public static void main(String[] args) {
        BankAccount bankAccount = new BankAccount(100); // Initialize account with balance of 100
        bankAccount.setBalance(200); // Works: sets balance to 200
        System.out.println(bankAccount.getBalance()); // Works: logs 200
        bankAccount.setBalance(-50); // Logs: "Balance cannot be negative."
    }
}

In this example, the setBalance method ensures that the balance is only set to non-negative values, adding a layer of validation.

By using getters and setters, you can incorporate logic for validating or processing data, making your class more flexible and secure while adhering to encapsulation principles.

Lesson Summary

Great job refreshing your understanding of encapsulation, private attributes, and private methods concepts in Java! Correctly understanding and applying these foundational principles of OOP make your code concise, robust, and secure.

Coming up next is hands-on practice. Keep up the good work — exciting exercises in Java are just around the corner!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal