Sobrescritura de métodos (tiempo de ejecución)
Parte de la sección Programación Orientada a Objetos del Journey de Java de Coddy — lección 29 de 87.
Mientras que la sobrecarga de métodos se resuelve en tiempo de compilación, la sobreescritura de métodos demuestra el polimorfismo en tiempo de ejecución. Aquí, Java decide qué método ejecutar cuando el programa se está ejecutando realmente, basándose en el tipo real del objeto en lugar del tipo declarado de la variable.
Aprendiste sobre la sobrescritura de métodos en el capítulo de Herencia. Lo que lo hace polimórfico es cómo Java lo maneja cuando una referencia de padre apunta a un objeto hijo:
public class Animal {
public void speak() {
System.out.println("Some sound");
}
}
public class Dog extends Animal {
@Override
public void speak() {
System.out.println("Bark!");
}
}
public class Cat extends Animal {
@Override
public void speak() {
System.out.println("Meow!");
}
}Ahora observa lo que sucede cuando usamos referencias de tipo padre:
Animal myPet = new Dog();
myPet.speak(); // Salida: Bark!
myPet = new Cat();
myPet.speak(); // Salida: Meow!Aunque myPet está declarada como Animal, Java llama al método sobrescrito basándose en el tipo de objeto real en tiempo de ejecución. Esto se llama despacho dinámico de métodos. La JVM observa lo que el objeto es realmente, no lo que dice la variable que es.
Esto permite patrones potentes como el procesamiento de diferentes objetos de manera uniforme:
Animal[] pets = {new Dog(), new Cat(), new Dog()};
for (Animal pet : pets) {
pet.speak(); // Cada uno llama a su propia versión
}El polimorfismo en tiempo de ejecución te permite escribir código flexible que funciona con tipos padre mientras utiliza automáticamente el comportamiento correcto de la clase hija.
Desafío
FácilConstruyamos un sistema de notificaciones que demuestre el polimorfismo en tiempo de ejecución en acción. Crearás una jerarquía de tipos de notificación donde la misma llamada a un método produce diferentes salidas dependiendo del tipo de objeto real en tiempo de ejecución.
Organizarás tu código en cuatro archivos:
Notification.java: Crea la clase base que comparten todas las notificaciones. Cada notificación tiene un camporecipient(String). Incluye un constructor para inicializarlo, un método getter y un métodosend()que imprima:Sending notification to [recipient]EmailNotification.java: Crea una clase que extienda de Notification. Las notificaciones por correo electrónico añaden un camposubject. Usasuperpara la inicialización del padre. Sobrescribe el métodosend()para que imprima:Emailing [recipient]: [subject]SMSNotification.java: Crea otra clase que extienda de Notification. Las notificaciones por SMS añaden un campophoneNumber. Sobrescribesend()para que imprima:Texting [phoneNumber] for [recipient]Main.java: ¡Aquí es donde brilla el polimorfismo en tiempo de ejecución! Recibirás tres entradas: un nombre de destinatario, un asunto de correo electrónico y un número de teléfono. Crea un arreglo de referencias deNotificationque contenga tres objetos en este orden: una Notification base, una EmailNotification y una SMSNotification (todas usando el mismo destinatario). Luego, recorre el arreglo y llama asend()en cada elemento. ¡Observa cómo Java llama automáticamente a la versión sobrescrita correcta basándose en lo que es realmente cada objeto!
Recibirás tres entradas: el nombre del destinatario (String), el asunto del correo electrónico (String) y el número de teléfono (String).
Tu salida debería mostrar tres líneas; cada llamada a send() produce una salida diferente a pesar de que estás llamando al mismo método en referencias de Notification. ¡Eso es el despacho dinámico de métodos en funcionamiento!
Hoja de referencia
La sobrescritura de métodos demuestra el polimorfismo en tiempo de ejecución, donde Java decide qué método ejecutar cuando el programa se está ejecutando, basándose en el tipo real del objeto en lugar del tipo declarado de la variable.
Cuando una referencia de padre apunta a un objeto hijo, Java llama al método sobrescrito basándose en el tipo de objeto real en tiempo de ejecución:
public class Animal {
public void speak() {
System.out.println("Some sound");
}
}
public class Dog extends Animal {
@Override
public void speak() {
System.out.println("Bark!");
}
}
Animal myPet = new Dog();
myPet.speak(); // Salida: Bark!
myPet = new Cat();
myPet.speak(); // Salida: Meow!Esto se llama despacho dinámico de métodos. La JVM observa lo que el objeto es realmente, no lo que dice la variable que es.
El polimorfismo en tiempo de ejecución permite procesar diferentes objetos de manera uniforme:
Animal[] pets = {new Dog(), new Cat(), new Dog()};
for (Animal pet : pets) {
pet.speak(); // Cada uno llama a su propia versión
}Pruébalo tú mismo
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Leer entradas
String recipient = scanner.nextLine();
String subject = scanner.nextLine();
String phoneNumber = scanner.nextLine();
// TODO: Crear un arreglo de referencias de Notification con 3 elementos
// El arreglo debe contener (en este orden):
// 1. Un objeto Notification base
// 2. Un objeto EmailNotification
// 3. Un objeto SMSNotification
// Todos usando el mismo recipient
// TODO: Recorrer el arreglo y llamar a send() en cada elemento
// ¡Esto demuestra el polimorfismo en tiempo de ejecución!
}
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosIntroducción a la OOPClases vs. ObjetosLa palabra clave thisMétodosCampos (Atributos)Método constructorSobrecarga de constructoresResumen - Calculadora simple4Herencia
Herencia básica (extends)La palabra clave superSobrescritura de métodos (@Override)Encadenamiento de constructoresLa clase ObjectHerencia simple y multinivelPor qué no hay herencia múltiple de clasesResumen - Jerarquía de empleados7Métodos especiales y la clase Object
Método toString()equals() y hashCode()Método clone()compareTo() y ComparableInterfaz ComparatorResumen - Ordenamiento personalizado2Modificadores de acceso y encapsulamiento
Visión general de los niveles de accesoMétodos Getter y SetterOcultamiento de informaciónLa palabra clave finalResumen - Gestor de cuentas bancarias5Polimorfismo
Fundamentos de sobrecarga de métodosSobrescritura de métodos (tiempo de ejecución)Upcasting y DowncastingEl operador instanceofClases y métodos abstractosResumen - Calculadora de figuras8Conceptos avanzados de POO
Composición vs. HerenciaAgregación vs. ComposiciónClases internas, anidadas y anónimasEnums y métodos de EnumRecords (Java 16+)Clases selladas (Java 17+)11Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón BuilderPatrón ObserverPatrón Strategy3Propiedades de clase y miembros estáticos
Variables de instancia vs. estáticasMétodos estáticosBloques estáticosConstantes (static final)Repaso - Contador y utilidad6Interfaces y Clases Abstractas
Introducción a las InterfacesImplementación de InterfacesImplementación de Múltiples InterfacesMétodos Default y Static en InterfacesClases Abstractas vs InterfacesInterfaces FuncionalesResumen - Sistema de Pagos9Genéricos
Introducción a los genéricosClases genéricasMétodos genéricosParámetros de tipo delimitadosComodines (?, extends, super)Resumen - Contenedor genérico12Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón Iterator