W3docs

Sobreescritura de métodos en Java

Sobreescribe métodos de la clase padre en subclases de Java con la anotación @Override y el despacho dinámico.

La sobreescritura ocurre cuando una subclase declara un método con la misma firma que uno heredado, reemplazando la versión del padre. Combinada con el polimorfismo, es el mecanismo que permite llamar a un método sobre una referencia de tipo padre y que se ejecute la implementación correcta de la subclase.

Este capítulo establece las reglas: qué cuenta como sobreescritura, qué no, qué cambios permite el compilador en la versión de la subclase y qué ventaja real aporta @Override.

Cómo se ve una sobreescritura

Un método sobreescribe a uno heredado cuando se cumplen todas estas condiciones:

  • Mismo nombre.
  • Misma lista de parámetros (tipos y orden, tras el borrado de genéricos).
  • Tipo de retorno igual al del padre o un subtipo de él (retorno covariante).
  • Visibilidad igual a la del padre o más amplia.
  • No declarado como final, static ni private en el padre.
class Animal {
  String speak() { return "(noise)"; }
}
class Cat extends Animal {
  @Override
  String speak() { return "meow"; }
}

Esta es una sobreescritura válida. Llamar a speak() sobre un Cat devuelve "meow"; llamarlo a través de una referencia de tipo Animal que apunta a un Cat también devuelve "meow" — el despacho se decide según la clase del objeto real.

@Override — úsalo siempre

@Override es una anotación que le indica al compilador "pretendo que esto sobreescriba un método heredado." Si realmente no sobreescribe ninguno — nombre incorrecto, parámetros incorrectos, tipo de retorno incorrecto — el compilador lanza un error:

class Cat extends Animal {
  @Override
  String Speak() { return "meow"; }    // ERROR — no Speak() in Animal
}

Sin la anotación, esto compilaría silenciosamente como un método nuevo llamado Speak, y ((Animal)cat).speak() seguiría devolviendo el "(noise)" del padre. La anotación no cuesta nada y detecta toda una familia de errores silenciosos. Escríbela siempre en las sobreescrituras.

Nota

Las sobreescrituras que escribirás con más frecuencia no son de tus propias clases — son de métodos heredados de Object, la raíz de todas las clases. Sobreescribir toString(), equals(Object) y hashCode() es la forma en que tus objetos se imprimen de manera legible, se comparan por valor y se comportan correctamente como claves en un HashMap o HashSet. @Override en estos métodos detecta el error clásico de escribir equals(Cat other) (una sobrecarga) en lugar de equals(Object other) (la sobreescritura real).

Sobreescritura vs sobrecarga

AspectoSobreescrituraSobrecarga
DóndeEntre un par subclase / superclaseEn la misma clase
FirmaDebe coincidir con la del padreDebe diferir de las demás
DespachoEn tiempo de ejecución, según el objeto realEn tiempo de compilación, según los tipos de argumento
Anotación@Overrideninguna

Se confunden constantemente. La sobreescritura es un método, varios tipos de objeto. La sobrecarga es varios métodos, un tipo, elegido según lo que se pasa.

Tipos de retorno covariantes

La subclase puede declarar un tipo de retorno más estrecho que el del padre:

class Animal {
  Animal makeBaby() { return new Animal(); }
}
class Cat extends Animal {
  @Override
  Cat makeBaby() { return new Cat(); }      // returns Cat, not Animal — fine
}

Esto es covarianza — el tipo de retorno de la sobreescritura es un subtipo del del padre. Es seguro porque cualquier llamador que espera un Animal de makeBaby() acepta gustosamente un Cat. La ventaja es que los llamadores que saben que tienen un Cat reciben un Cat de vuelta sin necesidad de conversión:

Cat kitten = new Cat().makeBaby();    // no cast needed

Ir en la dirección contraria (que una subclase amplíe el tipo de retorno) no está permitido — rompería a los llamadores que esperaban el tipo más estrecho.

La visibilidad solo puede ampliarse

La sobreescritura debe ser al menos tan visible como el método del padre:

class A { public  void hi() { } }
class B extends A { protected void hi() { } }   // ERROR — reduces visibility
class C extends A { public    void hi() { } }   // ok

Reducirla significaría que un llamador con una referencia de tipo A podría llamar a hi() pero, tras la asignación A a = new B(), ya no podría — lo que rompería la sustituibilidad.

Las excepciones solo pueden reducirse

Si el método padre no declara excepciones comprobadas, la sobreescritura tampoco puede declarar ninguna. Si el padre lanza una excepción comprobada, la sobreescritura puede lanzar la misma o una subclase más específica de ella — pero no una más amplia:

class A {
  void run() throws IOException { ... }
}
class B extends A {
  @Override void run() throws FileNotFoundException { ... }    // ok — FileNotFoundException extends IOException
}
class C extends A {
  @Override void run() throws Exception { ... }                // ERROR — too broad
}

Las excepciones no comprobadas (subclases de RuntimeException) no tienen restricciones — siempre puedes lanzarlas desde una sobreescritura.

Qué no se puede sobreescribir

  • Métodos private. No son visibles para la subclase en absoluto, por lo que un método con el mismo nombre en la subclase es simplemente un método nuevo.
  • Métodos final. Marcados específicamente para impedir sobreescrituras.
  • Métodos static. Una subclase puede declarar un método estático con el mismo nombre y firma, pero eso se llama ocultamiento de método, no sobreescritura. No hay polimorfismo — la JVM resuelve la llamada según el tipo en tiempo de compilación de la referencia:
class A { static String klass() { return "A"; } }
class B extends A { static String klass() { return "B"; } }

A a = new B();
System.out.println(a.klass());   // "A" — static, not polymorphic

Este es uno de los pocos casos donde la regla "el despacho de métodos elige la versión del objeto real" no se aplica.

Llamar al padre desde la sobreescritura

Un patrón común es extender en lugar de reemplazar el comportamiento del padre con super.method(...):

class Logger {
  void log(String s) { System.out.println(s); }
}
class TimestampedLogger extends Logger {
  @Override
  void log(String s) {
    super.log("[" + System.currentTimeMillis() + "] " + s);
  }
}

La sobreescritura decora el comportamiento del padre en lugar de duplicarlo. Se trata en el capítulo sobre la palabra clave super.

Un ejemplo completo

java— editable, runs on the server

Qué viene a continuación

Ya has visto cómo las subclases reemplazan los métodos del padre. La siguiente idea — la abstracción — es la cara opuesta: declarar un método que no tiene ningún cuerpo por defecto y obligar a cada subclase concreta a proporcionar uno. Continúa con Abstracción en Java.

Práctica

Práctica
En una subclase, ¿por qué vale la pena escribir @Override en cada sobreescritura aunque el código compilaría sin ella?
En una subclase, ¿por qué vale la pena escribir @Override en cada sobreescritura aunque el código compilaría sin ella?
Was this page helpful?