The encapsulation consists of limiting access to an object’s internal state and exposing controlled operations.
In the previous article, we left our Robot objects with their attributes exposed. Any part of the program could do this:
miRobot.nivelBateria = -5000; // Breaking the logic!
miRobot.nombre = null; // Crashing the systemIf we allow direct access to the data, we lose control. We cannot validate whether the data is correct.
This problem leads us to the second pillar of object-oriented programming: encapsulation.
Encapsulation consists of hiding the internal details of a class and exposing only what is safe and necessary.
Access Modifiers
Java provides four security levels to decide “who can see what”. They apply to classes, attributes, and methods.
| Modifier | Same Class | Same Package | Subclass (other package) | World (Everyone) |
|---|---|---|---|---|
public | ✅ | ✅ | ✅ | ✅ |
protected | ✅ | ✅ | ✅ | ❌ |
| Default | ✅ | ✅ | ❌ | ❌ |
private | ✅ | ❌ | ❌ | ❌ |
This is the most permissive level. Anyone from anywhere in the project can access it.
- Usage: Methods that are part of the public API (what you want others to use).
This is the most restrictive level. Only the class itself can see that data. Not even neighboring classes.
- Usage: ALL attributes should be private by default.
This is an intermediate level. It allows access to:
- The class itself.
- Classes in the same package.
- Subclasses (children), even if they are in another package.
- Usage: We will explore this in depth when we get to Inheritance.
If you do not write anything (neither public, nor private…), Java applies this level by default. It allows access to any class within the same package.
- Usage: Internal utility classes that only serve to help other classes in the module.
Getters and Setters: The Bouncers
If we make all attributes private (as it should be), no one will be able to read or change our Robot’s name. The object would be a useless black box.
To allow controlled access, we create special public methods: Getters (to read) and Setters (to write).
Why do this instead of leaving it public?
The advantage of a setter is that it allows validations to be executed before saving the data.
public class Robot {
// 1. PRIVATE ATTRIBUTE (Armored)
private int nivelBateria;
// 2. GETTER (Reading)
public int getNivelBateria() {
return this.nivelBateria;
}
// 3. SETTER (Controlled writing)
public void setNivelBateria(int nuevoNivel) {
// VALIDATION: Prevent absurd values
if (nuevoNivel < 0) {
System.out.println("Error: Battery cannot be negative. Setting it to 0.");
this.nivelBateria = 0;
} else if (nuevoNivel > 100) {
this.nivelBateria = 100;
} else {
this.nivelBateria = nuevoNivel;
}
}
}Now, from the outside:
Robot r = new Robot();
// r.nivelBateria = -50; // ERROR: Does not compile, it's private.
r.setNivelBateria(-50); // The setter intercepts the error and corrects it to 0.
System.out.println(r.getNivelBateria()); // We read safely.Read-Only Objects (Immutability)
If you want an attribute to be readable but not modifiable once created, simply do not create the Setter.
public class DNI {
private String numero;
public DNI(String n) {
this.numero = n;
}
// Only Getter. No one will be able to change the ID number after creation.
public String getNumero() {
return numero;
}
}