Skip to Content
Course content

34: Encapsulation and Access Modifiers

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Think about your car. To make it go faster, you press the gas pedal. To stop, you hit the brakes. You don't open the hood while driving and manually spray fuel into the cylinders or pull the brake cables by hand. Why? Because that would be chaotic, dangerous, and you'd probably break something. The car "encapsulates" the engine's complexity and only gives you a few safe, public interfaces—the pedals and the wheel—to interact with it.

In Java, encapsulation is exactly that. It's the practice of bundling the data (fields) and the methods that operate on that data into a single unit, and then hiding the internal state from the outside world. Here is how that maps to our code:

  • The Engine (Private Fields): These are the internal variables that the rest of the program shouldn't touch directly.
  • The Pedals (Public Methods): These are the "getters" and "setters" that let other classes interact with the data in a controlled way.
  • The Car's Computer (Validation): This is the logic inside your methods that prevents someone from, say, setting a car's speed to -500 mph.

Keeping the Guts Hidden

If you leave your class variables as public, any other class in your entire project can reach in and change them. I've seen this lead to some nightmare bugs where a variable was changed in a distant part of the codebase, and it took hours to find the culprit. To prevent this, we use the private modifier.

public class PlayerCharacter {
    private String name;
    private int health = 100;
    private int gold = 0;

    public PlayerCharacter(String name) {
        this.name = name;
    }
}

Now, if you try to do player.health = -50; from another class, Java will throw a compiler error. You've successfully locked the door. But now we have a problem: how do we actually use this data?

Controlled Access via Getters and Setters

We don't just make everything public again. Instead, we create methods that act as gatekeepers. I call these "the API of the class." This allows you to add validation logic. If you just let someone set the health directly, they could set it to a negative number, which makes no sense in a game. By using a setter, you control the rules.

public class PlayerCharacter {
    private int health = 100;

    // Getter: Let people see the health, but not change it directly
    public int getHealth() {
        return health;
    }

    // Setter: Let people change the health, but ONLY if it's valid
    public void setHealth(int newHealth) {
        if (newHealth < 0) {
            this.health = 0; // Clamp to zero
        } else if (newHealth > 100) {
            this.health = 100; // Cap at max health
        } else {
            this.health = newHealth;
        }
    }
}

See what happened there? The PlayerCharacter class now owns its own state. It decides what "valid" health looks like. The rest of your program doesn't need to know the rules; it just calls setHealth() and trusts the object to handle it correctly.

The Middle Ground: Protected and Default

Most of the time, you'll stick to public and private. But there are two other modifiers you'll run into as you start building larger systems. First is the "default" (or package-private) access. If you don't put any modifier at all, the variable is visible to any other class in the same folder (package), but hidden from everything else. It's a way of saying, "This is a secret from the world, but my teammates in this package can help me manage it."

Then there is protected. This is a bit more niche—it works like the default modifier, but it also allows subclasses (children) to access the field, even if they are in a different package. Use this sparingly. In my experience, if you find yourself using protected a lot, you might be making your class hierarchy too complex.




📋 Practical Task

Build a Secure Digital Wallet

Your task is to create a DigitalWallet class that prevents "illegal" financial states. A wallet should never have a negative balance, and you should be able to track how many transactions have occurred.

Requirements:

  • Create a private double field called balance and a private int called transactionCount.
  • Provide a public getter for the balance.
  • Provide a public method deposit(double amount). This method should only add money if the amount is positive. If it is, increment the transactionCount.
  • Provide a public method withdraw(double amount). This method should only deduct money if the amount is positive AND if there are enough funds in the balance. If successful, increment the transactionCount.
  • Provide a public getter for transactionCount, but do not provide a setter for it (it should only be changeable via deposits and withdrawals).

Test your class by trying to withdraw more money than you have and verifying that the balance does not go negative and the transaction count does not increase.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.