Encapsulation & Access Modifiers
Encapsulation & Access Modifiers
In the previous lessons you learned how to define fields and methods on a class, and how constructors set up initial state. You have been writing public fields without much thought about it. This lesson explains why that is often a problem, and how encapsulation — one of the four pillars of OOP — solves it.
The Problem with Public Fields
Imagine a BankAccount class where the balance is a public field:
Because balance is public, any code anywhere in the program can do this:
A bank account should never allow an arbitrary outside write to set a negative balance. The field has no protection; the class cannot enforce any rules about its own data. This is the problem encapsulation is designed to prevent.
The Four Access Modifiers
Java provides four levels of visibility, from most open to most closed:
public— accessible from anywhere: same class, same package, subclasses, and all other code.protected— accessible from the same package and from subclasses, even in other packages. Useful when designing for inheritance.- package-private (default) — no keyword at all. Accessible only within the same package. This is the default when you omit an access modifier.
private— accessible only within the class that declares it. Nothing outside — not even a subclass — can see it directly.
private for fields by default. Loosen the modifier only when you have a clear reason — do not start with public and hope nobody abuses it.
Encapsulation: Hiding State, Exposing Behaviour
Encapsulation means bundling state (fields) and behaviour (methods) together, and hiding the internal state behind a controlled interface. Outside code calls methods; only the class itself reads or writes its own fields directly.
Here is the BankAccount rewritten with encapsulation:
Now compare what outside code can do:
The class is now in full control of its own data. The compiler itself enforces the rules.
Where Each Modifier Applies
Access modifiers can be placed on fields, methods, constructors, and (with some restrictions) on classes. A concrete summary:
- Fields: almost always
private. Expose values through methods when needed. - Methods that form the public API:
public. - Helper methods used only inside the class:
private. - Methods shared within a package but not outside: package-private (default).
- Methods designed to be overridden by subclasses:
protected(covered in detail in the Inheritance lesson).
private method public later. But once you make something public and other code depends on it, removing that access is a breaking change. Hiding implementation details buys you the freedom to change them later without breaking callers.
Default (Package-Private) Access in Practice
When you omit the modifier entirely, the member is visible only to classes in the same package — useful for internal utility methods that should not be part of the public API but need to be shared across several classes in the same module.
A Complete Example: Temperature Sensor
Here is a self-contained example that uses several modifiers together:
MIN_CELSIUSisprivate— an internal constant; callers do not need to know it exists.currentTemperatureandunitareprivate— state hidden from the outside.recordReadingandgetCelsiusarepublic— the controlled interface callers use.toCelsiusisprivate— a helper that makes the implementation cleaner but is nobody else's business.
private to public solely for test access leaks implementation details and weakens encapsulation.
Summary
Encapsulation protects an object's internal state by making fields private and providing controlled access through public methods. Java's four access levels — private, package-private, protected, and public — let you precisely control what each piece of code can see. Always start with the most restrictive access and widen only when there is a clear need. In the next lesson you will turn this principle into a repeatable pattern using getters, setters, and field-level validation.