Semana 11 - Modificadores y Palabras Clave en POO en Java
Modificadores y Palabras Clave en POO en Java
En Java, los modificadores y palabras clave se dividen en dos categorías principales: 1. Modificadores de acceso: Controlan la visibilidad de clases, métodos y atributos. 2. Modificadores no relacionados con acceso: Afectan el comportamiento o las características de los elementos.
Además, hay palabras clave específicas que se usan frecuentemente en el contexto de POO para gestionar instancias, referencias y verificaciones de tipo. A continuación, se detalla cada uno de estos aspectos.
Modificadores de Acceso
Los modificadores de acceso determinan quién puede acceder a una clase, método, atributo o constructor. Java tiene cuatro niveles de acceso:
1. public
- Descripción: El elemento es accesible desde cualquier clase, sin restricciones.
- Uso en POO: Se usa para exponer métodos y atributos que deben ser accesibles universalmente, como métodos de una API pública o clases principales.
- Ejemplo:
public class Persona { public String nombre; // Atributo accesible desde cualquier clase public void saludar() { // Método accesible desde cualquier clase System.out.println("Hola, soy " + nombre); } } class Main { public static void main(String[] args) { Persona persona = new Persona(); persona.nombre = "Juan"; // Acceso directo al atributo público persona.saludar(); // Salida: Hola, soy Juan } }
2. protected
- Descripción: El elemento es accesible dentro del mismo paquete y también en las subclases, incluso si están en diferentes paquetes.
- Uso en POO: Útil en herencia para permitir que las subclases accedan a atributos o métodos de la superclase, manteniendo cierto nivel de encapsulamiento.
- Ejemplo:
package paquete1; public class Animal { protected String especie; // Accesible en el paquete y en subclases protected void hacerSonido() { System.out.println("El animal hace un sonido"); } } package paquete2; import paquete1.Animal; public class Perro extends Animal { public void setEspecie() { especie = "Canino"; // Acceso permitido porque es protected hacerSonido(); // Acceso permitido } } class Main { public static void main(String[] args) { Perro perro = new Perro(); perro.setEspecie(); // Salida: El animal hace un sonido } }
3. Acceso por defecto (package-private)
- Descripción: Si no se especifica un modificador de acceso, el elemento es accesible solo dentro del mismo paquete.
- Uso en POO: Se usa para limitar el acceso a clases, métodos o atributos a un grupo específico de clases dentro del mismo paquete, promoviendo el encapsulamiento.
- Ejemplo:
Nota: Si intentas acceder a
package paquete1; class Coche { // Acceso por defecto (solo en paquete1) String marca = "Toyota"; void mostrarMarca() { System.out.println("Marca: " + marca); } } public class Main { public static void main(String[] args) { Coche coche = new Coche(); coche.mostrarMarca(); // Salida: Marca: Toyota // Esto funciona porque Main está en el mismo paquete } }
Coche
desde otro paquete, obtendrás un error de compilación.
4. private
- Descripción: El elemento solo es accesible dentro de la misma clase.
- Uso en POO: Es clave para el encapsulamiento, ya que protege los datos internos de una clase y fuerza el acceso a través de métodos públicos (como getters y setters).
- Ejemplo:
public class CuentaBancaria { private double saldo; // Solo accesible dentro de la clase public void depositar(double monto) { if (monto > 0) { saldo += monto; } } public double getSaldo() { return saldo; // Acceso controlado al atributo privado } } class Main { public static void main(String[] args) { CuentaBancaria cuenta = new CuentaBancaria(); cuenta.depositar(100.0); System.out.println("Saldo: " + cuenta.getSaldo()); // Salida: Saldo: 100.0 // cuenta.saldo = 200; // Error: saldo es private } }
Modificadores No Relacionados con Acceso
Estos modificadores afectan el comportamiento o las propiedades de clases, métodos o atributos, y son fundamentales en POO para definir cómo se usan los elementos.
1. static
- Descripción: Indica que un atributo o método pertenece a la clase, no a las instancias. Solo existe una copia de un elemento
static
, compartida por todos los objetos. - Uso en POO:
- Atributos
static
: Variables compartidas, como contadores o configuraciones globales. - Métodos
static
: Métodos utilitarios que no dependen del estado de un objeto (ejemplo:Math.sqrt()
). - Ejemplo:
public class Contador { private static int instanciasCreadas = 0; // Atributo estático public Contador() { instanciasCreadas++; // Incrementa el contador al crear un objeto } public static int getInstanciasCreadas() { // Método estático return instanciasCreadas; } } class Main { public static void main(String[] args) { Contador c1 = new Contador(); Contador c2 = new Contador(); System.out.println("Instancias creadas: " + Contador.getInstanciasCreadas()); // Salida: Instancias creadas: 2 } }
2. final
- Descripción: Indica que un elemento no puede ser modificado después de su inicialización.
- Variables
final
: Son constantes (no se pueden reasignar). - Métodos
final
: No pueden ser sobrescritos por subclases. - Clases
final
: No pueden ser heredadas. - Uso en POO: Garantiza inmutabilidad, protege la lógica de métodos críticos y evita la extensión no deseada de clases.
- Ejemplo:
Nota: Intentar heredar de
public final class Configuracion { // Clase no heredable public final int MAX_USUARIOS = 100; // Constante public final void iniciarSistema() { // Método no sobrescribible System.out.println("Sistema iniciado con máximo " + MAX_USUARIOS + " usuarios"); } } class Main { public static void main(String[] args) { Configuracion config = new Configuracion(); config.iniciarSistema(); // Salida: Sistema iniciado con máximo 100 usuarios // config.MAX_USUARIOS = hotspot:6 200; // Error: MAX_USUARIOS es final } }
Configuracion
o sobrescribiriniciarSistema
generará un error de compilación.
3. abstract
- Descripción: Se usa en clases y métodos para indicar que no están completamente implementados.
- Clases
abstract
: No pueden instanciarse y pueden contener métodos abstractos o concretos. - Métodos
abstract
: No tienen implementación y deben ser implementados por las subclases. - Uso en POO: Permite definir un esqueleto común para un grupo de clases, promoviendo la abstracción y la herencia.
- Ejemplo:
public abstract class Vehiculo { protected String marca; public Vehiculo(String marca) { this.marca = marca; } public abstract void acelerar(); // Método abstracto public void mostrarMarca() { // Método concreto System.out.println("Marca: " + marca); } } public class Coche extends Vehiculo { public Coche(String marca) { super(marca); } @Override public void acelerar() { System.out.println("El coche está acelerando"); } } class Main { public static void main(String[] args) { // Vehiculo v = new Vehiculo("Toyota"); // Error: Vehiculo es abstracto Coche coche = new Coche("Toyota"); coche.mostrarMarca(); // Salida: Marca: Toyota coche.acelerar(); // Salida: El coche está acelerando } }
4. synchronized
- Descripción: Se usa en métodos o bloques para garantizar que solo un hilo acceda a un recurso a la vez en entornos multihilo.
- Uso en POO: Protege el estado de objetos compartidos en aplicaciones concurrentes, asegurando consistencia de datos.
- Ejemplo:
public class ContadorSincronizado { private int valor = 0; public synchronized void incrementar() { valor++; } public int getValor() { return valor; } } class Main { public static void main(String[] args) throws InterruptedException { ContadorSincronizado contador = new ContadorSincronizado(); // Crear dos hilos que incrementan el contador Runnable tarea = () -> { for (int i = 0; i < 1000; i++) { contador.incrementar(); } }; Thread hilo1 = new Thread(tarea); Thread hilo2 = new Thread(tarea); hilo1.start(); hilo2.start(); hilo1.join(); hilo2.join(); System.out.println("Valor final: " + contador.getValor()); // Salida: Valor final: 2000 (sin synchronized, podría ser menor) } }
5. transient
- Descripción: Indica que un atributo no debe ser serializado cuando un objeto se convierte en un flujo de bytes (usado con
Serializable
). - Uso en POO: Útil para excluir datos sensibles o temporales (como contraseñas o cachés) durante la serialización.
- Ejemplo:
import java.io.*; public class Usuario implements Serializable { private String nombre; private transient String contraseña; // No se serializa public Usuario(String nombre, String contraseña) { this.nombre = nombre; this.contraseña = contraseña; } @Override public String toString() { return "Usuario{nombre='" + nombre + "', contraseña='" + contraseña + "'}"; } } class Main { public static void main(String[] args) throws IOException, ClassNotFoundException { Usuario usuario = new Usuario("Ana", "secreto"); // Serializar ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("usuario.ser")); out.writeObject(usuario); out.close(); // Deserializar ObjectInputStream in = new ObjectInputStream(new FileInputStream("usuario.ser")); Usuario usuarioDeserializado = (Usuario) in.readObject(); in.close(); System.out.println(usuarioDeserializado); // Salida: Usuario{nombre='Ana', contraseña='null'} } }
6. volatile
- Descripción: Indica que una variable puede ser modificada por múltiples hilos, asegurando que los cambios sean visibles inmediatamente para todos los hilos.
- Uso en POO: Útil en concurrencia para evitar problemas de caché en variables compartidas.
- Ejemplo:
public class Controlador { private volatile boolean activo = true; public void detener() { activo = false; } public void ejecutar() { while (activo) { System.out.println("Ejecutando..."); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Detenido"); } } class Main { public static void main(String[] args) throws InterruptedException { Controlador controlador = new Controlador(); Thread hilo = new Thread(controlador::ejecutar); hilo.start(); Thread.sleep(2000); // Esperar 2 segundos controlador.detener(); // Detener el bucle hilo.join(); } }
Palabras Clave Relacionadas con POO
Estas palabras clave son esenciales para trabajar con objetos, herencia y verificaciones de tipo en Java.
1. this
- Descripción: Hace referencia a la instancia actual de la clase. Se usa para:
- Diferenciar entre atributos de instancia y parámetros/métodos locales.
- Invocar otros constructores de la misma clase.
- Uso en POO: Mejora la claridad del código y permite la sobrecarga de constructores.
- Ejemplo:
public class Estudiante { private String nombre; private int edad; public Estudiante(String nombre) { this(nombre, 0); // Llama al constructor con dos parámetros } public Estudiante(String nombre, int edad) { this.nombre = nombre; // Diferencia atributo de parámetro this.edad = edad; } public void mostrarInfo() { System.out.println("Nombre: " + this.nombre + ", Edad: " + this.edad); } } class Main { public static void main(String[] args) { Estudiante estudiante = new Estudiante("Luis"); estudiante.mostrarInfo(); // Salida: Nombre: Luis, Edad: 0 } }
2. super
- Descripción: Hace referencia a la superclase inmediata. Se usa para:
- Acceder a constructores, métodos o atributos de la superclase.
- Llamar al constructor de la superclase desde una subclase.
- Uso en POO: Esencial en herencia para reutilizar o extender el comportamiento de la superclase.
- Ejemplo:
public class Animal { protected String nombre; public Animal(String nombre) { this.nombre = nombre; } public void hacerSonido() { System.out.println("Sonido genérico"); } } public class Gato extends Animal { public Gato(String nombre) { super(nombre); // Llama al constructor de Animal } @Override public void hacerSonido() { super.hacerSonido(); // Llama al método de la superclase System.out.println("Miau!"); } } class Main { public static void main(String[] args) { Gato gato = new Gato("Luna"); gato.hacerSonido(); // Salida: Sonido genérico // Miau! } }
3. instanceof
- Descripción: Verifica si un objeto es una instancia de una clase, interfaz o subclase específica.
- Uso en POO: Útil para realizar conversiones de tipo seguras (downcasting) o para implementar lógica condicional basada en tipos.
- Ejemplo:
public interface Volador { void volar(); } public class Pajaro implements Volador { @Override public void volar() { System.out.println("El pájaro vuela"); } } public class Coche { public void conducir() { System.out.println("El coche conduce"); } } class Main { public static void main(String[] args) { Object obj = new Pajaro(); if (obj instanceof Pajaro) { Pajaro pajaro = (Pajaro) obj; // Downcasting seguro pajaro.volar(); // Salida: El pájaro vuela } if (obj instanceof Volador) { Volador volador = (Volador) obj; volador.volar(); // Salida: El pájaro vuela } if (!(obj instanceof Coche)) { System.out.println("No es un coche"); // Salida: No es un coche } } }
Resumen de Modificadores y Palabras Clave
Categoría | Elemento | Descripción |
---|---|---|
Modificadores de Acceso | public |
Accesible desde cualquier clase. |
protected |
Accesible en el mismo paquete y en subclases. | |
(por defecto) | Accesible solo en el mismo paquete. | |
private |
Accesible solo dentro de la misma clase. | |
Modificadores No de Acceso | static |
Pertenece a la clase, no a instancias. |
final |
Impide modificación (variables), sobrescritura (métodos) o herencia (clases). | |
abstract |
Define clases/métodos sin implementación completa. | |
synchronized |
Controla acceso en entornos multihilo. | |
transient |
Excluye atributos de la serialización. | |
volatile |
Garantiza visibilidad de cambios en variables entre hilos. | |
Palabras Clave Relacionadas | this |
Referencia a la instancia actual. |
super |
Referencia a la superclase. | |
instanceof |
Verifica el tipo de un objeto. |
Buenas Prácticas con Modificadores y Palabras Clave
- Usa
private
por defecto: Protege los datos internos y usa getters/setters para acceso controlado. - Minimiza el uso de
public
: Solo expón lo necesario para mantener el encapsulamiento. - Usa
final
para constantes y clases inmutables: Mejora la seguridad y claridad. - Evita abusar de
static
: Los elementos estáticos pueden romper el diseño orientado a objetos si se usan como variables globales. - Usa
synchronized
con cuidado: Considera alternativas modernas comoLock
o clases concurrentes. - Verifica tipos con
instanceof
antes de downcasting: Evita excepciones de tipoClassCastException
. - Documenta el uso de
super
ythis
: Mejora la legibilidad del código.