CodeGym /Cursos /JAVA 25 SELF /Errores con modificadores de acceso

Errores con modificadores de acceso

JAVA 25 SELF
Nivel 23 , Lección 2
Disponible

1. Introducción

En Java, los modificadores de acceso son como un sistema de cerraduras en una casa. Determinan quién y desde dónde puede «entrar» en tu habitación (o al campo/método de tu clase). Si todas las puertas están abiertas — cualquiera puede venir y cambiar algo. Si todo está cerrado — nadie podrá romper nada, pero tú mismo en algún momento puedes quedarte encerrado.

Recordemos que en Java hay cuatro niveles de acceso principales:

Modificador Disponible dentro de la clase Disponible en el paquete Disponible en subclases Disponible en otros paquetes
private
(package)
protected
(a través de la herencia)
public

(package) — es cuando el modificador no se especifica explícitamente. Un miembro de clase así solo es visible dentro de un único paquete.

2. Errores típicos con los modificadores de acceso

Error 1: Campos y métodos con modificador por defecto (package-private)

El error más común de los principiantes es olvidar indicar el modificador de acceso. Como resultado, el campo o método pasa a estar disponible en todo el paquete, aunque no lo hubieras planeado. Esto puede provocar que otra clase (en el mismo paquete, pero no relacionada con la tuya) pueda modificar el estado interno de tu objeto.

// Error: el campo name no está protegido!
class User {
    String name; // package-private!
}

Como resultado, cualquier clase de este paquete puede escribir:

User user = new User();
user.name = "Vasya"; // ¡sin restricciones!

Error 2: Violación de la encapsulación — campos abiertos (public)

El segundo error más frecuente es declarar los campos de la clase como public. Es cómodo cuando estás aprendiendo o escribes un ejemplo corto, pero en proyectos reales casi siempre es mala idea. Pierdes el control sobre quién y cómo cambia tus datos.

public class Account {
    public double balance; // ¡PELIGROSO!
}

Ahora cualquier código puede hacer:

Account acc = new Account();
acc.balance = -1000000; // ¿Y ahora de quién es la culpa?

Error 3: Ausencia de getters y setters

A veces el desarrollador hace los campos private, pero olvida añadir métodos para gestionarlos. Como resultado, obtener o cambiar el valor es imposible incluso donde sería apropiado.

public class Product {
    private String name;
    // No hay ni getName() ni setName()
}

Error 4: Intento de acceder a miembros private desde otra clase

Si declaraste un campo o método como private, no se puede acceder a él desde otra clase, incluso si está en el mismo paquete. A los principiantes a menudo les sorprende por qué «no ve» el campo.

public class User {
    private String password;
}

public class UserService {
    public void resetPassword(User user) {
        // user.password = "123"; // ¡Error de compilación!
    }
}

Error 5: Errores con protected

Muchos creen que protected significa «visible en todas partes donde hay herencia». Pero en Java el acceso a miembros protected fuera del paquete es posible solo a través de la herencia y únicamente para la subclase. Es un detalle fácil de pasar por alto.

package animals;

public class Animal {
    protected void sleep() {}
}

package zoo;
import animals.Animal;

public class Dog extends Animal {
    public void test() {
        sleep(); // OK — subclase
    }
}

public class NotADog {
    public void test() {
        Animal a = new Animal();
        // a.sleep(); // Error: ¡no es subclase!
    }
}

3. Cómo hacerlo bien: mejores prácticas

Regla 1: Por defecto, haz que los campos sean private

Este es el principio principal de la encapsulación. Los campos deben estar ocultos para todos excepto para la propia clase. Si necesitas dar acceso, usa getters/setters.

public class Book {
    private String title;
    private int pages;

    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
}

Regla 2: Expón solo los métodos necesarios

Si un método debe ser accesible desde fuera, hazlo public. Si solo se necesita dentro del paquete, déjalo como package-private. Si el método está destinado únicamente a las subclases, usa protected.

Regla 3: Minimiza el ámbito de visibilidad

Cuanto menor es el ámbito de visibilidad, menor es la probabilidad de errores accidentales y de «invitados inesperados». No hagas métodos y campos public si no es necesario.

Regla 4: Usa getters y setters para controlar el acceso

Esto permite añadir lógica adicional al leer/escribir el campo, por ejemplo, validación.

public class Account {
    private double balance;

    public void setBalance(double balance) {
        if (balance < 0) {
            throw new IllegalArgumentException("¡El saldo no puede ser negativo!");
        }
        this.balance = balance;
    }

    public double getBalance() {
        return balance;
    }
}

Regla 5: No expongas la implementación interna

Si tienes un array o una lista como campo, no la devuelvas directamente mediante el getter — devuelve una copia o proporciona solo los métodos necesarios.

public class Team {
    private List<String> members = new ArrayList<>();

    // Correcto:
    public List<String> getMembers() {
        return new ArrayList<>(members); // devolvemos una copia
    }
}

4. Ejemplos en la práctica

Supongamos que tenemos la clase LibraryUser, que describe a un usuario de la biblioteca.

Ejemplo de implementación incorrecta

public class LibraryUser {
    public String name;
    public int borrowedBooks;
}

En este estado, cualquier código puede hacer lo que quiera con el objeto:

LibraryUser user = new LibraryUser();
user.name = null;
user.borrowedBooks = -10; // ¿Lógica? ¿Qué lógica?

Ejemplo de implementación correcta con encapsulación

public class LibraryUser {
    private String name;
    private int borrowedBooks;

    public LibraryUser(String name) {
        this.name = name;
        this.borrowedBooks = 0;
    }

    public String getName() {
        return name;
    }

    public int getBorrowedBooks() {
        return borrowedBooks;
    }

    public void borrowBook() {
        borrowedBooks++;
    }

    public void returnBook() {
        if (borrowedBooks > 0) {
            borrowedBooks--;
        }
    }
}

Ahora el código externo no puede cambiar directamente la cantidad de libros tomados ni el nombre del usuario. Todo se controla únicamente a través de los métodos de la clase.

5. Particularidades y matices de implementación

A veces parece que es más fácil hacer un campo public que escribir un montón de getters y setters. ¡Pero es una trampa! Un campo abierto es como una puerta abierta del piso: sí, es cómodo, pero no muy seguro.

Otro detalle más: no siempre necesitas crear getters y setters para todos los campos. Si el valor de un campo no debe cambiar después de crear el objeto, crea solo el getter y haz el campo final:

public class Passport {
    private final String number;

    public Passport(String number) {
        this.number = number;
    }

    public String getNumber() {
        return number;
    }
}

Además, recuerda: si una clase se declara como public, ¡el nombre del archivo debe coincidir con el nombre de la clase! Esto no es exactamente sobre modificadores de acceso, pero sí un error muy común entre los principiantes.

6. Errores comunes al trabajar con los modificadores de acceso

Error 1: olvidaste indicar el modificador de acceso en un campo o método. Como resultado, el campo o método pasa a estar disponible en todo el paquete, incluso si no lo querías. Indica siempre el modificador explícitamente, aunque el IDE no se queje.

Error 2: todos los campos declarados como public. Esto mata la encapsulación, vuelve tu código vulnerable e impredecible. El hábito de los ejemplos «por simplicidad» no debe llegar al código de producción.

Error 3: intento de acceder a un campo private desde otra clase. Java no lo permitirá — el compilador te protegerá, pero si de repente quisiste «saltarte» esto mediante reflexión, piensa por qué surgió esa necesidad.

Error 4: esperar que los miembros protected estén disponibles en cualquier lugar donde haya herencia. En realidad, fuera del paquete solo se puede acceder a ellos desde una subclase y solo a través de this o mediante un objeto de la subclase.

Error 5: devolver la colección interna mediante el getter. Si devuelves una referencia al array o lista internos, el código externo podrá modificarla, rompiendo los invariantes de la clase.

Error 6: ausencia de control al establecer el valor mediante el setter. Si no validas el valor entrante, puedes obtener un estado incorrecto del objeto (por ejemplo, un saldo negativo).

Error 7: ámbito de visibilidad demasiado amplio de los métodos. A veces se hacen métodos public cuando solo se necesitan dentro del paquete o de la clase. Esto abre una API innecesaria y complica el mantenimiento.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION