Nivel 1 · 20 min
Herencia y Polimorfismo
La herencia permite que una clase (subclase) herede atributos y comportamientos de otra (superclase). El polimorfismo permite que el mismo código trabaje con objetos de distintos tipos — el tipo dinámico en tiempo de ejecución determina qué método se ejecuta. Juntos son poderosos pero requieren disciplina para no crear jerarquías frágiles.
Herencia y sobreescritura
En Java, extends hereda implementación. Una subclase puede sobreescribir métodos (override) con @Override. El método sobreescrito debe tener la misma firma o covariance en el tipo de retorno. La palabra super permite llamar al método de la superclase. Regla clave: sobreescribir es para especializar comportamiento, no para reemplazarlo completamente. Si reemplazás todo el comportamiento del padre, la herencia no agrega valor.
Polimorfismo y dispatch
El polimorfismo de subtipos permite que una referencia de tipo Animal apunte a objetos Dog, Cat, Bird. Cuando llamás animal.sound(), el JVM ejecuta el método del tipo dinámico (Dog.sound()) no del tipo de referencia (Animal.sound()). Esto es late binding o dynamic dispatch. El polimorfismo hace que el código sea extensible — podés agregar nuevas subclases sin modificar el código que trabaja con la superclase.
Covarianza, contravarianza y LSP
Covarianza: un subtipo puede devolver un subtipo más específico que el padre (covariant return types en Java). Contravarianza: un subtipo puede aceptar un supertipo más general como parámetro. El Principio de Sustitución de Liskov (LSP) formaliza la regla: si S es subtipo de T, los objetos de T pueden ser reemplazados por objetos de S sin alterar el comportamiento. La prueba: si tu código que espera Animal falla cuando recibe Dog, violás LSP.
Code example
// Polimorfismo: mismo codigo, distintos comportamientos
List'<'Shape'>' shapes = List.of(new Circle(5), new Rectangle(3, 4));
double totalArea = shapes.stream()
.mapToDouble(Shape::area) // dispatch dinamico
.sum();
// Circle.area() y Rectangle.area() se llaman correctamente