1. Resumen de los modificadores de acceso
En Java hay cuatro niveles de acceso para clases, campos, métodos y constructores:
| Modificador | ¿Dónde es accesible? |
|---|---|
|
En todas partes: dentro de la clase, en otras clases y en otros paquetes |
|
Dentro de la clase, en las subclases (herederas) y en otras clases del mismo paquete |
|
Solo dentro del paquete (si no se especifica el modificador explícitamente) |
|
Solo dentro de la clase actual |
Vamos a analizar cada uno con más detalle — con ejemplos, bromas y giros inesperados.
public — acceso público
public es como un anuncio para todo el mundo: «¡Entrada permitida para todos!». Si una clase, un campo, un método o un constructor se declara como public, se puede acceder a él desde cualquier otra clase, incluso desde otro paquete.
Ejemplo:
public class Cat {
public String name;
public void sayMeow() {
System.out.println("¡Miau!");
}
}
A esta clase y a sus campos/métodos se puede acceder desde cualquier lugar. Es útil si quieres que tu clase esté disponible para todos: por ejemplo, si estás escribiendo una biblioteca.
¡Pero! Los campos públicos no siempre son una buena idea (véase la lección anterior). Normalmente solo se hacen públicos los métodos que deben estar disponibles desde fuera, mientras que los campos casi siempre se dejan private.
private — acceso solo dentro de la clase
private es como una caja fuerte con combinación: nadie, excepto la propia clase, puede acceder a esos miembros. ¡Ni siquiera las subclases ven los campos y métodos privados!
Ejemplo:
public class Cat {
private String secretName;
public void setSecretName(String name) {
secretName = name;
}
public String getSecretName() {
return secretName;
}
}
Aquí secretName no se puede leer ni modificar directamente desde otra clase. Solo el propio Cat puede hacerlo (o sus métodos). Esta es la base de la encapsulación: ocultamos los detalles internos y proporcionamos acceso a ellos únicamente a través de métodos.
protected — acceso protegido
protected es como un pase VIP: el acceso está permitido a la clase, a sus subclases (incluso si están en otros paquetes) y a todas las clases dentro del paquete actual.
Ejemplo:
public class Animal {
protected int age;
protected void growOlder() {
age++;
}
}
Ahora cualquier clase que herede de Animal podrá acceder al campo age y al método growOlder().
public class Cat extends Animal {
public void haveBirthday() {
growOlder();
System.out.println("¡El gato cumple " + age + " años!");
}
}
Además, todos los miembros protected son accesibles para las clases dentro del mismo paquete.
(package-private) — acceso dentro del paquete
Si no especificas ningún modificador de acceso, el miembro de la clase se considera package-private (o «acceso por defecto»). Es como una puerta sin cerradura, pero solo para los tuyos: el acceso está permitido solo a las clases del mismo paquete.
Ejemplo:
class Dog {
String name; // package-private
void bark() { // package-private
System.out.println("¡Guau!");
}
}
La clase Dog, su campo name y el método bark() son accesibles solo dentro del mismo paquete. Intentar acceder a ellos desde otro paquete provocará un error de compilación.
2. Aplicación de los modificadores a campos y métodos
Por qué los campos casi siempre se hacen private
Los campos de una clase son su estado interno. Si los dejas públicos, cualquier código externo podrá cambiarlos en cualquier momento. Es como si permitieras que niños desconocidos jueguen con tus muebles como si fueran Lego: un día te despertarás y el frigorífico estará en el baño patas arriba.
Ejemplo de mala encapsulación:
public class Person {
public String name;
public int age;
}
Ejemplo de buena encapsulación:
public class Person {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Los métodos (getters y setters) permiten controlar cómo exactamente las clases externas pueden modificar los campos. Por ejemplo, se puede prohibir una edad negativa.
Cuándo hacer los métodos public, protected o package-private
- public — si el método debe estar disponible para todos. Normalmente es la funcionalidad principal de la clase.
- protected — si el método solo es necesario para las subclases o dentro del paquete (por ejemplo, métodos auxiliares que pueden ser útiles en clases hijas).
- package-private — si el método solo se necesita dentro del paquete, pero no debe estar disponible externamente (por ejemplo, detalles internos de implementación).
- private — si el método se usa únicamente dentro de la propia clase (por ejemplo, métodos auxiliares para la lógica interna).
Ejemplo:
public class BankAccount {
private double balance;
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
protected void applyInterest() {
balance *= 1.05;
}
void internalAudit() {
// package-private: solo para clases dentro del paquete
}
private void logAction(String action) {
// solo para necesidades internas de la clase
}
}
3. Ejemplos de código: una clase con distintos niveles de acceso
Vamos a crear una clase que lo tenga todo: miembros públicos, privados, protegidos y de paquete. De paso intentaremos acceder a ellos desde otras clases y veremos qué sucede.
package zoo;
public class Animal {
public String publicName = "Disponible para todos";
protected String protectedName = "Solo para subclases y paquete";
String packageName = "Solo para el paquete";
private String privateName = "Solo para Animal";
public void publicMethod() {
System.out.println("Método público");
}
protected void protectedMethod() {
System.out.println("Método protegido");
}
void packageMethod() {
System.out.println("Método de paquete");
}
private void privateMethod() {
System.out.println("Método privado");
}
}
Ahora intentemos acceder a estos miembros desde otra clase en el mismo paquete:
package zoo;
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
System.out.println(animal.publicName); // OK
System.out.println(animal.protectedName); // OK
System.out.println(animal.packageName); // OK
System.out.println(animal.privateName); // Error: private
animal.publicMethod(); // OK
animal.protectedMethod(); // OK
animal.packageMethod(); // OK
animal.privateMethod(); // Error: private
}
}
Y ahora intentemos acceder a estos miembros desde otro paquete:
package other;
import zoo.Animal;
public class Test {
public static void main(String[] args) {
Animal animal = new Animal();
System.out.println(animal.publicName); // OK
System.out.println(animal.protectedName); // Error: protected
System.out.println(animal.packageName); // Error: package-private
System.out.println(animal.privateName); // Error: private
animal.publicMethod(); // OK
animal.protectedMethod(); // Error: protected
animal.packageMethod(); // Error: package-private
animal.privateMethod(); // Error: private
}
}
Conclusión:
- public — accesible en todas partes.
- protected — accesible dentro del paquete y en las subclases (incluso desde otros paquetes, si es a través de la herencia).
- package-private — solo dentro del paquete.
- private — solo dentro de la clase.
4. Mejores prácticas: cómo elegir el modificador de acceso
Minimiza el alcance
Cuanto menos código pueda ver tu campo o método, mejor. Expón solo lo que realmente necesita el código externo. Esto se llama principio del menor privilegio (principle of least privilege).
- Los campos casi siempre deben ser private. Las excepciones son únicamente las verdaderas constantes (public static final), pero de ello hablaremos con más detalle en las siguientes lecciones.
- Los métodos se hacen public solo si forman parte de la interfaz externa de la clase.
- Los métodos auxiliares (lógica interna) — private.
- Métodos para subclases — protected.
- Métodos de servicio internos para el paquete — package-private.
¿Por qué es importante?
- Cualquier cambio en los detalles de implementación puede romper el código de otros si esos detalles están expuestos.
- La clase se vuelve más difícil de probar y mantener.
- Los errores accidentales (por ejemplo, modificar un campo de forma incorrecta) pueden llevar a fallos.
A veces los principiantes piensan: «¿Para qué tanta complicación?, ¡que todo sea public!». Pero luego, cuando el proyecto crece, toca reescribir medio programa solo porque alguien modificaba directamente los campos de la clase.
5. Errores típicos al trabajar con modificadores de acceso
Error n.º 1: Dejaste campos public o package-private por defecto.
Si no se especifica un modificador, el campo o método estará disponible para todas las clases dentro del paquete. Esto puede conducir a situaciones inesperadas si alguien empieza a cambiar tus campos directamente.
Error n.º 2: Intento de acceder a un miembro private desde otra clase.
El compilador no lo permitirá: obtendrás un error. Y si decides sortearlo mediante reflection (reflexión), bienvenido al mundo de los bugs y caídas inesperadas.
Error n.º 3: Demasiado public.
Si declaras todo como public, la clase se parece a una caja abierta llena de cables — cualquiera puede tirar del cable equivocado y romperlo todo.
Error n.º 4: No usas protected para métodos que solo necesitan las subclases.
Si un método solo es necesario para su extensión en clases hijas, decláralo protected, no public.
Error n.º 5: Visibilidad package-private implícita.
A veces se olvida especificar el modificador y el método queda accesible para todo el paquete. Esto puede ser una sorpresa si pensabas que era private.
GO TO FULL VERSION