CodeGym /Cursos /JAVA 25 SELF /Sobrescritura de métodos (overriding): diferencia con la ...

Sobrescritura de métodos (overriding): diferencia con la sobrecarga

JAVA 25 SELF
Nivel 18 , Lección 2
Disponible

1. Sobrescritura de métodos

La sobrescritura (overriding) — es la posibilidad de escribir en una subclase tu propia versión de un método que ya existe en la clase padre. Gracias a ello funciona el verdadero polimorfismo durante la ejecución del programa (run-time).

En pocas palabras:
Si tienes una clase base con un método y quieres que la subclase ejecute ese método a su manera, simplemente declaras un método con la misma signatura en la subclase. Al invocar el método a través de una referencia del tipo base, se llamará a la versión del método del tipo real (efectivo) del objeto.

Ejemplo de la vida real.
Imagina que tienes un equipo de animales y pides a cada uno «emitir un sonido». Para todos es el mismo comando makeSound(), pero el perro ladra, el gato maúlla y la vaca muge. En el código parece la llamada al mismo método, pero el resultado es distinto: ¡he aquí la magia de la sobrescritura!

Sintaxis de la sobrescritura

Ejemplo básico

class Animal {
    void makeSound() {
        System.out.println("El animal emite algún sonido");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("¡Guau!");
    }
}

class Cat extends Animal {
    @Override
    void makeSound() {
        System.out.println("¡Miau!");
    }
}

Aquí en la clase Animal se define el método makeSound(). Las subclases Dog y Cat sobrescriben este método, proporcionando su propia implementación.

Anotación @Override

En Java se recomienda (y es una buena costumbre) marcar los métodos sobrescritos con la anotación @Override:

@Override
void makeSound() { ... }

No es obligatorio para que el código funcione, pero:

  • El compilador comprobará que realmente estás sobrescribiendo un método (y no te has equivocado por accidente en el nombre o en los parámetros).
  • Mejora la legibilidad del código: a otros desarrolladores les queda claro que este método sobrescribe al del padre.

Llamada a un método sobrescrito

Animal myDog = new Dog();
myDog.makeSound(); // Imprimirá: ¡Guau!

Aunque la variable es de tipo Animal, en realidad apunta a un objeto Dog, y se invoca el método de la clase Dog. Esto es el enlace dinámico (tardío).

2. Diferencia entre sobrescritura (overriding) y sobrecarga (overloading)

Sobrecarga (overloading)

  • En una misma clase (o dentro de la jerarquía, pero siempre «cerca»).
  • Los métodos tienen el mismo nombre, pero parámetros distintos (tipo, cantidad, orden).
  • La elección del método se realiza en tiempo de compilación.
void print(int x) { ... }
void print(String s) { ... }

Sobrescritura (overriding)

  • En clases distintas: el método se declara en la superclase y se sobrescribe en la subclase.
  • Los métodos tienen el mismo nombre y la misma signatura (parámetros y tipo de retorno).
  • La elección del método se realiza en tiempo de ejecución (run-time), en función del tipo real del objeto.

Tabla comparativa

Sobrecarga (overloading) Sobrescritura (overriding)
Dónde En una misma clase En superclase y subclase
Nombre del método Igual Igual
Parámetros Distintos Iguales
Tipo de retorno Puede diferir Debe coincidir o ser un subtipo
Cuándo se decide Compilación Ejecución (run-time)
Anotación No es necesaria @Override (recomendado)

3. Reglas de la sobrescritura de métodos

La sobrescritura es poderosa, pero tiene reglas estrictas. Vamos a verlas en orden.

La signatura del método debe coincidir

  • El nombre del método, los tipos y el orden de los parámetros deben ser idénticos a los del método en la superclase.
  • El tipo de retorno debe coincidir o ser covariante (es decir, un subtipo del tipo de retorno del método padre).

Ejemplo con tipo de retorno covariante:

class Animal {
    Animal reproduce() { return new Animal(); }
}
class Cat extends Animal {
    @Override
    Cat reproduce() { return new Cat(); } // OK: Cat es un subtipo de Animal
}

Modificador de acceso

El modificador de acceso del método sobrescrito no puede ser más restrictivo que el del método en la superclase. Si el método en el padre es public, en la subclase debe seguir siendo public. No puedes hacerlo menos accesible (protected o private).

Ejemplo:

class Parent {
    public void greet() { }
}
class Child extends Parent {
    // void greet() { } // Error: el modificador por defecto — package-private — es menos accesible que public
    @Override
    public void greet() { } // OK
}

Excepciones

  • El método sobrescrito no puede lanzar nuevas excepciones checked que no estén declaradas en el método base.
  • Se puede lanzar menos o las mismas excepciones.

Ejemplo:

class Parent {
    void doWork() throws IOException { }
}
class Child extends Parent {
    @Override
    void doWork() throws FileNotFoundException { } // OK, FileNotFoundException es un subtipo de IOException
    // void doWork() throws SQLException { } // Error: SQLException no está declarado en el padre
}

Los métodos estáticos no se sobrescriben

Los métodos estáticos pueden ocultarse (hidden), pero no sobrescribirse. Si declaras un método estático con la misma signatura en la subclase, ¡no es sobrescritura! Será simplemente ocultación del método, no polimorfismo.

class Animal {
    static void info() { System.out.println("Animal"); }
}
class Dog extends Animal {
    static void info() { System.out.println("Dog"); }
}

La llamada Dog.info() imprimirá "Dog", pero si se invoca a través de una variable de tipo Animal, se llamará al método Animal.info(). ¡Esto no es polimorfismo!

Los métodos final no se pueden sobrescribir

Si un método en la superclase está declarado como final, intentar sobrescribirlo producirá un error de compilación.

class Animal {
    final void sleep() { }
}
class Dog extends Animal {
    // @Override
    // void sleep() { } // Error: no se puede sobrescribir un método final
}

4. Ejemplos prácticos

Veamos en la práctica cómo funciona la sobrescritura y en qué se diferencia de la sobrecarga.

Ejemplo 1: Clase Shape y sus derivados

class Shape {
    void draw() {
        System.out.println("Dibujamos una figura");
    }
}

class Circle extends Shape {
    @Override
    void draw() {
        System.out.println("Dibujamos un círculo");
    }
}

class Rectangle extends Shape {
    @Override
    void draw() {
        System.out.println("Dibujamos un rectángulo");
    }
}

Usamos polimorfismo:

public class Main {
    public static void main(String[] args) {
        Shape s1 = new Circle();
        Shape s2 = new Rectangle();
        s1.draw(); // Dibujamos un círculo
        s2.draw(); // Dibujamos un rectángulo
    }
}

Aunque las variables están declaradas como Shape, se invoca el método de la clase a la que realmente pertenece el objeto.

Ejemplo 2: Diferencia con la sobrecarga

class Printer {
    void print(String s) {
        System.out.println("Cadena: " + s);
    }

    void print(int n) {
        System.out.println("Número: " + n);
    }
}

Aquí ambos métodos se llaman print, pero tienen parámetros distintos: esto es sobrecarga, no sobrescritura.

5. Sobrescritura y llamada al método del padre (super)

A veces, en un método sobrescrito quieres primero ejecutar la lógica del padre y luego añadir la tuya. Para ello se usa la palabra clave super.

class Animal {
    void makeSound() {
        System.out.println("El animal emite un sonido");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        super.makeSound(); // Llamada al método del padre
        System.out.println("¡Guau!");
    }
}

La llamada new Dog().makeSound() imprimirá:

El animal emite un sonido
¡Guau!

Cómo funciona el enlace dinámico (late binding)

Cuando llamas a un método a través de una referencia de tipo base, Java en tiempo de ejecución mira a qué objeto real pertenece esa referencia y llama exactamente a la versión del método definida en la clase de ese objeto.

Animal a = new Cat();
a.makeSound(); // Llamará a Cat.makeSound(), no a Animal.makeSound()

Esta es la base del polimorfismo en Java.

6. Cómo se relaciona con tu aplicación

En nuestra aplicación educativa (por ejemplo, un sistema de gestión de empleados) puedes crear una clase base Employee con el método work(), y las subclases Manager y Developer pueden implementar ese método a su manera:

class Employee {
    void work() {
        System.out.println("El empleado trabaja");
    }
}

class Manager extends Employee {
    @Override
    void work() {
        System.out.println("El gerente dirige");
    }
}

class Developer extends Employee {
    @Override
    void work() {
        System.out.println("El desarrollador escribe código");
    }
}

Ahora puedes almacenar a todos los empleados en un mismo array o lista:

Employee[] employees = {new Manager(), new Developer(), new Developer()};
for (Employee e : employees) {
    e.work(); // ¡Para cada uno, su propia salida!
}

7. Errores típicos al sobrescribir métodos

Error n.º 1: error tipográfico en el nombre del método o en los parámetros. Si te equivocas en el nombre del método o en sus parámetros, no estarás sobrescribiendo el método, sino creando uno nuevo. Como resultado, el polimorfismo no funciona. Por eso usa siempre la anotación @Override: el compilador te avisará de inmediato.

Error n.º 2: modificador de acceso más restrictivo. Si en el padre el método es public, y en la clase hija lo declaras como protected o sin modificador, obtendrás un error de compilación.

Error n.º 3: intentar sobrescribir un método static o final. Los métodos estáticos no se sobrescriben, y los métodos final no se pueden sobrescribir en absoluto. Si lo intentas, el compilador te detendrá.

Error n.º 4: cambiar el tipo de retorno por uno incompatible. Si el tipo de retorno del método en la subclase no coincide con el tipo del método en la superclase (y no es su subtipo), el compilador no permitirá sobrescribirlo.

Error n.º 5: añadir nuevas excepciones checked. Un método sobrescrito no puede lanzar nuevas excepciones checked que no estén en la declaración del método base. Si lo haces, el compilador dará un error.

Error n.º 6: olvidarse de super. Si en el método sobrescrito quieres conservar parte del comportamiento del padre, no olvides llamar explícitamente a super.methodName(). Java no lo hará por ti.

Ahora ya sabes cómo funciona la sobrescritura de métodos, en qué se diferencia de la sobrecarga y cómo se implementa con ella el polimorfismo en Java. En la siguiente lección veremos cómo aplicar el polimorfismo en la práctica — con colecciones, arrays y tareas reales.

1
Tarea
JAVA 25 SELF, nivel 18, lección 2
Bloqueada
Voces del zoológico: Sobrescritura de método en una subclase
Voces del zoológico: Sobrescritura de método en una subclase
1
Tarea
JAVA 25 SELF, nivel 18, lección 2
Bloqueada
Magia de la impresión: diferencia entre sobrescritura y sobrecarga
Magia de la impresión: diferencia entre sobrescritura y sobrecarga
1
Tarea
JAVA 25 SELF, nivel 18, lección 2
Bloqueada
El arte de dibujar: uso de la anotación @Override
El arte de dibujar: uso de la anotación @Override
1
Tarea
JAVA 25 SELF, nivel 18, lección 2
Bloqueada
Presentación musical de la mascota: Invocar el método del padre con super
Presentación musical de la mascota: Invocar el método del padre con super
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION