Herencia: Conceptos Básicos y Ejemplos en Java

La herencia es uno de los pilares de la programación orientada a objetos. Permite que una clase adquiera las propiedades y comportamientos de otra, promoviendo la reutilización del código y una estructura más clara.

1. ¿Qué es la herencia?

La herencia permite que una clase (llamada subclase) herede atributos y métodos de otra clase (llamada superclase). Esto crea una relación «es un» entre las clases.

Sintaxis básica:

class Subclase extends Superclase {
    // Nuevos atributos y métodos de la subclase
}

Ejemplo básico:

public class Animal {
    public void comer() {
        System.out.println("Este animal está comiendo.");
    }
}

public class Perro extends Animal {
    public void ladrar() {
        System.out.println("El perro está ladrando.");
    }
}

public class Principal {
    public static void main(String[] args) {
        Perro miPerro = new Perro();
        miPerro.comer(); // Método heredado
        miPerro.ladrar(); // Método de la subclase
    }
}

Salida:

Este animal está comiendo.
El perro está ladrando.

2. Características clave de la herencia

  1. Reutilización del código:
    • Las subclases pueden usar atributos y métodos definidos en la superclase.
  2. Especialización:
    • Las subclases pueden agregar atributos y métodos adicionales para ampliar o modificar el comportamiento de la superclase.
  3. Relación jerárquica:
    • Java no permite herencia múltiple directa (una clase no puede extender más de una clase).

Ejemplo de jerarquía:

public class Animal {
    public void hacerSonido() {
        System.out.println("El animal hace un sonido.");
    }
}

public class Gato extends Animal {
    @Override
    public void hacerSonido() {
        System.out.println("El gato maúlla.");
    }
}

3. Uso de super

La palabra clave super se utiliza para:

Llamar al constructor de la superclase:

public class Animal {
    public Animal(String nombre) {
        System.out.println("Animal: " + nombre);
    }
}

public class Perro extends Animal {
    public Perro(String nombre) {
        super(nombre); // Llama al constructor de la superclase
        System.out.println("Perro: " + nombre);
    }
}

Salida:

Animal: Max
Perro: Max

Acceder a métodos o atributos de la superclase:

public class Animal {
    public void dormir() {
        System.out.println("El animal está durmiendo.");
    }
}

public class Perro extends Animal {
    public void dormir() {
        super.dormir(); // Llama al método de la superclase
        System.out.println("El perro está roncando.");
    }
}

Salida:

El animal está durmiendo.
El perro está roncando.

4. Modificadores de acceso y herencia

ModificadorAccesible en subclases del mismo paqueteAccesible en subclases de otros paquetes
public
protected
Sin modificadorNo
privateNoNo

5. Sobreescritura de métodos (@Override)

La sobreescritura permite que una subclase redefina el comportamiento de un método heredado.

Reglas de sobreescritura:

  1. El método debe tener el mismo nombre, tipo de retorno y parámetros.
  2. El método no puede tener un modificador más restrictivo.
  3. Se recomienda usar la anotación @Override.

Ejemplo:

public class Animal {
    public void hacerSonido() {
        System.out.println("El animal hace un sonido.");
    }
}

public class Gato extends Animal {
    @Override
    public void hacerSonido() {
        System.out.println("El gato maúlla.");
    }
}

public class Principal {
    public static void main(String[] args) {
        Animal miAnimal = new Gato();
        miAnimal.hacerSonido(); // Llama al método de la subclase
    }
}

Salida:

El gato maúlla.

6. Clases finales y abstractas

Clases finales (final):

public final class Animal {
    // Esta clase no puede tener subclases
}

Clases abstractas:

public abstract class Animal {
    public abstract void hacerSonido();
}

public class Perro extends Animal {
    @Override
    public void hacerSonido() {
        System.out.println("El perro ladra.");
    }
}

7. Errores comunes con la herencia

  1. Acceso restringido a métodos heredados:
    • Si un método heredado es private, no está disponible en la subclase.
  2. Dependencia circular:
    • Java no permite que dos clases se extiendan mutuamente.
  3. Herencia innecesaria:
    • Usa herencia solo si existe una clara relación «es un».

Resumen

  • La herencia permite reutilizar código y crear jerarquías de clases.
  • Usa super para acceder a constructores y métodos de la superclase.
  • La sobreescritura (@Override) redefine comportamientos en las subclases.
  • Entiende las diferencias entre clases finales y abstractas para aplicarlas correctamente.