1. Errores al heredar
La herencia es uno de los pilares de la POO, pero también uno de los temas en los que los desarrolladores principiantes tropiezan con más frecuencia. Veamos los errores clásicos y aprendamos a evitarlos.
Falta de llamada al constructor de la clase base (super(...))
Cuando creas una subclase, es importante recordar que la clase base puede requerir cierta inicialización mediante su constructor. Si en la clase base no hay un constructor por defecto (sin parámetros), entonces en el constructor de la subclase es obligatorio invocar explícitamente el constructor de la clase base con super(...).
Ejemplo de error:
class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
}
class Dog extends Animal {
// ¡Error! Animal no tiene constructor por defecto
public Dog() {
// super(); // el compilador inserta super() automáticamente, pero ¡ese constructor no existe!
}
}
Cómo corregirlo:
class Dog extends Animal {
public Dog(String name) {
super(name); // ¡Todo bien!
}
}
Comentario:
Si en la clase base solo hay constructores con parámetros, el compilador no añadirá automáticamente un constructor sin parámetros. Esta es una causa frecuente de errores de compilación.
Intentar heredar de una clase final o sobrescribir un método final
En Java puedes declarar una clase o un método como final. Esto significa:
- No se puede heredar de la clase.
- No se puede sobrescribir (override) el método en las subclases.
Ejemplo de error:
final class Cat {}
// ¡Error de compilación!
class Tiger extends Cat {
// ...
}
class Animal {
public final void sleep() {
System.out.println("Zzz...");
}
}
class Dog extends Animal {
// ¡Error de compilación!
@Override
public void sleep() {
System.out.println("Dog is sleeping...");
}
}
Comentario:
Si ves el error «cannot inherit from final», «cannot override final method», ¡revisa los modificadores!
Incumplimiento del principio de sustitución de Liskov (Liskov Substitution Principle)
Suena complejo, pero en la práctica significa: un objeto de una subclase debe comportarse como un objeto de la clase base, sin romper la lógica del programa. Un error habitual es sobrescribir métodos de tal manera que la nueva clase se comporte de forma distinta a lo esperado por la clase base.
Ejemplo:
class Bird {
public void fly() {
System.out.println("¡Estoy volando!");
}
}
class Penguin extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException("Los pingüinos no vuelan");
}
}
¿Cuál es el problema?
El código que trabaja con Bird espera que cualquier ave pueda volar. Pero si le pasas un Penguin, el programa puede fallar.
Mejor enfoque:
En estos casos conviene replantear la jerarquía o usar interfaces/composición.
2. Errores con la sobrecarga de métodos (overloading)
La sobrecarga es cuando en una misma clase hay varios métodos con el mismo nombre pero con parámetros diferentes. Parece sencillo, pero aquí también hay trampas.
Sobrecarga en lugar de sobrescritura (error en la firma)
A menudo, los principiantes quieren sobrescribir (override) un método de la clase base, pero cambian accidentalmente sus parámetros. Como resultado, se obtiene una sobrecarga, no una sobrescritura, ¡y el polimorfismo no funciona!
Ejemplo de error:
class Animal {
public void makeSound() {
System.out.println("Some sound");
}
}
class Dog extends Animal {
// Querían sobrescribir, pero hicieron una sobrecarga
public void makeSound(String extra) {
System.out.println("Bark! " + extra);
}
}
Problema:
La llamada dog.makeSound() invocará el método padre, no tu método nuevo.
La llamada dog.makeSound("loudly") invocará el sobrecargado, ¡pero el polimorfismo no funciona!
Mejor práctica:
Usa la anotación @Override: si te equivocas en la firma, el compilador te avisará al instante.
@Override
public void makeSound() { /* ... */ }
Comportamiento no obvio en la sobrecarga (conversión automática de tipos, ambigüedad en la llamada)
A veces Java puede «elegir» un método distinto al que esperabas si los parámetros coinciden con varias sobrecargas.
public class OverloadDemo {
public void print(int x) {
System.out.println("int: " + x);
}
public void print(double x) {
System.out.println("double: " + x);
}
}
OverloadDemo demo = new OverloadDemo();
demo.print(5); // int: 5
demo.print(5.0); // double: 5.0
demo.print(5L); // long -> double: double: 5.0
Problema:
Si llamas a demo.print(5L), Java elegirá print(double x) (ya que long se convierte mejor a double que a int).
Si existen métodos con parámetros de tipo Object, Integer, int, una llamada con null puede provocar un error de compilación: «reference to print is ambiguous».
Usar el mismo nombre de método con distinto tipo de retorno (error de compilación)
En Java no puedes declarar dos métodos con el mismo nombre y la misma lista de parámetros que solo difieran en el tipo de retorno.
public class Demo {
// Error de compilación
public int foo() { return 1; }
public String foo() { return "hello"; }
}
Explicación:
La firma de un método para sobrecarga es nombre + parámetros. El tipo de retorno no se tiene en cuenta. El compilador no podrá entender qué método quieres invocar.
3. Mejores prácticas
Para evitar tropiezos con la herencia y la sobrecarga, sigue estas recomendaciones:
Usa siempre la anotación @Override en los métodos que vayas a sobrescribir
No solo mejora la legibilidad, también te protege de errores en la firma. Si cambias accidentalmente los parámetros o el nombre del método, el compilador te avisará de inmediato.
@Override
public void makeSound() {
System.out.println("Bark!");
}
Distingue claramente entre sobrecarga y sobrescritura
- Sobrescritura (override): cambias el comportamiento del método del padre; la firma debe coincidir.
- Sobrecarga (overload): añades un método nuevo con el mismo nombre, pero con parámetros distintos.
Tabla ilustrativa:
| Sobrecarga (overloading) | Sobrescritura (overriding) | |
|---|---|---|
| Dónde | En una misma clase/jerarquía | En la subclase |
| Nombre del método | Coincide | Coincide |
| Parámetros | Diferentes | Coinciden |
| Valor de retorno | Puede diferir | Debe coincidir/ser compatible |
| Anotación | No es necesaria | Se recomienda @Override |
No abuses de la sobrecarga
Si un método tiene demasiadas variantes sobrecargadas, el código se vuelve ilegible y confuso. Es mejor usar objetos de parámetros o el patrón Builder si hay demasiadas variantes.
4. Ejemplo: sistema de registro de mascotas
Supón que haces un sistema sencillo de registro de mascotas. Tienes una clase base Pet y las subclases Cat y Dog.
public class Pet {
private String name;
public Pet(String name) {
this.name = name;
}
public void speak() {
System.out.println(name + " emite un sonido indescifrable.");
}
}
public class Cat extends Pet {
public Cat(String name) {
super(name);
}
@Override
public void speak() {
System.out.println(getName() + " dice: ¡Miau!");
}
// Error: no existe el método getName() en Pet
}
Error típico:
Al intentar sobrescribir un método, accedemos a un método que no existe en la clase base. Es mejor añadir un getter:
public class Pet {
private String name;
public Pet(String name) { this.name = name; }
public String getName() { return name; }
public void speak() { System.out.println(name + " emite un sonido indescifrable."); }
}
Ahora todo funciona correctamente y podemos usar polimorfismo:
Pet myPet = new Cat("Rex");
myPet.speak(); // Rex dice: ¡Miau!
5. Errores típicos con herencia y sobrecarga
Error n.º 1: olvidaste llamar a super(...).
Si la clase base tiene lógica importante en el constructor y no la llamas, el programa puede comportarse de forma inesperada o ni siquiera compilar.
Error n.º 2: sobrescribiste el método equivocado.
Querías cambiar el comportamiento del método del padre, pero en realidad añadiste un método nuevo con un nombre parecido o con otros parámetros. Resultado: el método antiguo sigue funcionando como antes y tu método nuevo no lo llama nadie.
Error n.º 3: intentaste sobrescribir un método final.
Java no te permitirá hacerlo (¡y está bien!). Si ves un error de compilación, busca final.
Error n.º 4: sobrecargaste el método hasta hacerlo irreconocible.
Cuando tienes 10 variantes del método calculate y tú mismo te confundes sobre cuál se invoca, es hora de parar y pensar en refactorizar.
Error n.º 5: violaste el principio de Liskov.
Si tu subclase cambia el significado del comportamiento de la clase base, toda la arquitectura puede «descarrilar». Por ejemplo, si tienes una clase Shape con el método getArea() y una subclase BrokenShape devuelve -1, esto puede provocar bugs extraños.
GO TO FULL VERSION