Java

Tutorial Java: Herencia

Java herencia: conceptos y ejemplos prácticos. Aprende los conceptos de herencia en Java y cómo implementarlos con ejemplos prácticos.

Aprende Java GRATIS y certifícate

La herencia es un principio fundamental de la Programación Orientada a Objetos (POO) que posibilita la creación de nuevas clases a partir de clases existentes. Este concepto facilita la reutilización de código y la implementación de relaciones jerárquicas entre clases.

Java, como un lenguaje orientado a objetos, incorpora la herencia como uno de sus mecanismos principales para la definición y creación de clases. En este texto, se explorará el concepto de herencia en detalle utilizando Java SE como base.

Concepto de Herencia

La herencia es un mecanismo que permite a una nueva clase adquirir los miembros (atributos y métodos) de una clase existente. La clase de la cual se heredan los miembros se llama clase padre o superclase, y la clase que hereda se llama clase hija o subclase.

La herencia permite que las subclases hereden campos y métodos de las superclases. Esto significa que se puede definir un comportamiento común en la superclase y heredar ese comportamiento en las subclases, lo que promueve la reutilización de código.

La sintaxis para la herencia en Java es simple. Se utiliza la palabra clave extends para indicar que una clase hereda de otra. A continuación, se muestra un ejemplo básico:

public class Animal {
    public void comer() {
        System.out.println("El animal come");
    }
}

public class Perro extends Animal {
    // La clase Perro hereda los métodos de la clase Animal
}

En este ejemplo, la clase Perro hereda de la clase Animal. Esto significa que Perro tiene acceso al método comer(). Se puede verificar esto creando un objeto Perro y llamando al método comer():

public class Main {
    public static void main(String[] args) {
        Perro perro = new Perro();
        perro.comer();  // Esto imprimirá "El animal come"
    }
}

Como se puede ver, no es necesario definir el método comer() en la clase Perro. La herencia permite reutilizar el código de la superclase.

Jerarquía de Clases

En Java, todas las clases, ya sean definidas por el usuario o predefinidas en el lenguaje, son subclases de la clase Object. Esta es la superclase de todas las demás clases en Java y proporciona métodos que son heredados por todas las clases en Java. Estos métodos incluyen toString(), equals(), hashCode(), getClass(), entre otros.

public class Main {
    public static void main(String[] args) {
        Perro perro = new Perro();
        System.out.println(perro.toString());  // Esto es posible porque Perro hereda de Object
    }
}

La herencia en Java permite una jerarquía de clases donde una subclase puede ser una superclase para otra subclase, formando una cadena de herencia. Cada subclase hereda directamente de su superclase inmediata y también indirectamente de todas sus superclases.

public class Animal {
    public void comer() {
        System.out.println("El animal come");
    }
}

public class Mamifero extends Animal {
    public void amamantar() {
        System.out.println("El mamífero amamanta");
    }
}

public class Perro extends Mamifero {
    public void ladrar() {
        System.out.println("El perro ladra");
    }
}

En este caso, la clase Perro hereda tanto de Mamifero como de Animal (a través de Mamifero), así como de Object (a través de Animal). Por lo tanto, un objeto de la clase Perro puede llamar a los métodos comer(), amamantar() y ladrar(), así como a cualquier método heredado de Object.

Modificadores de Acceso y Herencia

La herencia en Java también está influenciada por los modificadores de acceso: private, public y protected. Los miembros private de la superclase no son heredados por la subclase, mientras que los miembros public y protected sí lo son.

El modificador de acceso protected es especialmente importante en el contexto de la herencia. Un miembro protected de una superclase es accesible dentro de su propio paquete (como un miembro de paquete) y también en sus subclases (incluso fuera del paquete).

A continuación se muestra un ejemplo:

public class Animal {
    private String nombre;
    protected int edad;

    public void comer() {
        System.out.println("El animal come");
    }
}

public class Perro extends Animal {
    public void ladrar() {
        System.out.println("El perro ladra");
        System.out.println("La edad del perro es " + edad); // esto es válido
        System.out.println("El nombre del perro es " + nombre); // esto dará error
    }
}

En este ejemplo, la clase Perro puede acceder al campo edad, que está marcado como protected, pero no puede acceder al campo nombre, que está marcado como private. Por lo tanto, el campo nombre no es heredado por la clase Perro.

Método Super en Java

Java proporciona una palabra clave especial, super, que se puede usar para acceder a los miembros de la superclase que han sido ocultados por los miembros de la subclase. Esto es útil cuando se ha sobreescrito un método en la subclase pero todavía se desea tener acceso a la implementación del método en la superclase. La palabra clave 'super' en Java se utiliza para referirse a la superclase inmediata desde la cual una clase está heredando. Esto puede ser útil cuando necesitamos acceder a los miembros de la superclase que han sido ocultados por los miembros de la subclase.

public class Animal {
    public void hacerRuido() {
        System.out.println("El animal hace ruido");
    }
}

public class Perro extends Animal {
    @Override
    public void hacerRuido() {
        super.hacerRuido();  // llama al método hacerRuido() de la superclase Animal
        System.out.println("El perro ladra");
    }
}

En este ejemplo, cuando se llama al método hacerRuido() en un objeto de la clase Perro, primero se llama al método hacerRuido() de la superclase Animal (gracias a la llamada super.hacerRuido()), y luego se imprime "El perro ladra". Esto muestra cómo se puede usar super para acceder a los métodos de la superclase.

Constructores y Herencia

En Java, los constructores no son directamente heredados por las subclases, pero hay reglas especiales para cómo los constructores de la superclase son llamados cuando se crea un objeto de la subclase. Esto es especialmente importante si la superclase tiene argumentos en su constructor, o si la subclase necesita llamar a un constructor específico en la superclase. En el contexto de los constructores, la palabra clave 'this' en Java se utiliza para referirse al objeto actual. En combinación con 'super', 'this' puede usarse para llamar a diferentes constructores en la superclase en función de los argumentos proporcionados.

En general, cada constructor en una subclase (ya sea provisto por el desarrollador o el constructor predeterminado proporcionado por Java) comienza con una llamada implícita o explícita al constructor de la superclase, utilizando super o this.

Cuando se crea un objeto de una subclase, el constructor de la superclase se ejecuta primero. Esto asegura que la inicialización apropiada de los campos heredados de la superclase ocurra antes de que se inicialicen los campos de la subclase.

Si no se proporciona un constructor en la subclase, Java llamará automáticamente al constructor sin argumentos de la superclase. Si la superclase no tiene un constructor sin argumentos, o si se desea llamar a un constructor diferente, se debe hacerlo explícitamente con la palabra clave super.

public class Animal {
    public Animal() {
        System.out.println("Un animal ha sido creado");
    }
}

public class Perro extends Animal {
    public Perro() {
        super();  // llama al constructor de Animal
        System.out.println("Un perro ha sido creado");
    }
}

En este ejemplo, cuando se crea un Perro, se llamará al constructor de Animal antes que al constructor de Perro, imprimiendo "Un animal ha sido creado" antes de "Un perro ha sido creado".

Polimorfismo y Herencia

La herencia es una de las bases del polimorfismo en Java. El polimorfismo permite que una referencia de la superclase pueda referirse a un objeto de cualquiera de sus subclases. Esto es útil porque permite la reutilización de código al utilizar tipos generales en lugar de tipos específicos.

Aquí hay un ejemplo:

public class Animal {
    public void hacerRuido() {
        System.out.println("El animal hace ruido");
    }
}

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

public class Gato extends Animal {
    @Override
    public void hacerRuido() {
        System.out.println("El gato maulla");
    }
}

public class Main {
    public static void hacerRuidoAnimal(Animal animal) {
        animal.hacerRuido();
    }

    public static void main(String[] args) {
        Perro perro = new Perro();
        Gato gato = new Gato();

        hacerRuidoAnimal(perro);  // imprime "El perro ladra"
        hacerRuidoAnimal(gato);   // imprime "El gato maulla"
    }
}

En este ejemplo, el método hacerRuidoAnimal() puede aceptar cualquier objeto que sea una subclase de Animal y llamar a su método hacerRuido(). Gracias al polimorfismo, el método correcto se llama en tiempo de ejecución basándose en el tipo real del objeto, no en el tipo de la referencia.

Limitaciones de la Herencia en Java

En Java, una clase sólo puede extender de una superclase a la vez. Esto se llama herencia simple y es una limitación diseñada para evitar ciertos problemas, como la ambigüedad en la herencia de múltiples superclases con métodos de igual firma.

Aunque la herencia múltiple (heredar de varias superclases a la vez) no está permitida en Java, una clase puede implementar múltiples interfaces. Esto permite un tipo de herencia múltiple, pero sólo para métodos abstractos (métodos sin cuerpo).

Conclusión

La herencia es un poderoso principio de la programación orientada a objetos que promueve la reutilización de código y permite representar relaciones de tipo "es un" entre las clases. Java proporciona un fuerte soporte para la herencia, con muchas características y herramientas, desde la herencia simple de una superclase con extends, hasta la herencia de múltiples interfaces con implements.

Es importante tener en cuenta que aunque la herencia puede ser muy útil, no siempre es la mejor solución. En algunos casos, la composición (tener objetos de otras clases como miembros) puede ser una mejor opción. Se debe hacer un buen diseño para decidir cuándo usar la herencia y cuándo usar la composición.

Aprende Java GRATIS online

Ejercicios de esta lección Herencia

Evalúa tus conocimientos de esta lección Herencia con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Clases abstractas

Test

Streams: reduce()

Test

Streams: flatMap()

Test

Llamada y sobrecarga de funciones

Puzzle

Métodos referenciados

Test

Métodos de la clase String

Código

Representación de Fecha

Puzzle

Operadores lógicos

Test

Tipos de datos

Código

Estructuras de iteración

Puzzle

Streams: forEach()

Test

Objetos

Puzzle

Funciones lambda

Test

Uso de Scanner

Puzzle

CRUD en Java de modelo Customer sobre un ArrayList

Proyecto

Tipos de variables

Puzzle

Streams: collect()

Puzzle

Operadores aritméticos

Puzzle

Interfaz funcional Consumer

Test

API java.nio 2

Puzzle

API Optional

Test

Interfaz funcional Function

Test

Encapsulación

Test

Interfaces

Código

Uso de API Optional

Puzzle

Representación de Hora

Test

Herencia básica

Test

Clases y objetos

Código

Interfaz funcional Supplier

Puzzle

HashMap

Puzzle

Sobrecarga de métodos

Test

Polimorfismo de tiempo de ejecución

Puzzle

OOP en Java

Proyecto

Creación de Streams

Test

Streams: min max

Puzzle

Métodos avanzados de la clase String

Puzzle

Polimorfismo de tiempo de compilación

Test

Excepciones

Puzzle

Herencia avanzada

Puzzle

Estructuras de selección

Test

Uso de interfaces

Test

HashSet

Test

Objeto Scanner

Test

Streams: filter()

Puzzle

Operaciones de Streams

Puzzle

Interfaz funcional Predicate

Puzzle

Streams: sorted()

Test

Configuración de entorno

Test

CRUD en Java de modelo Customer sobre un HashMap

Proyecto

Uso de variables

Test

Clases

Test

Streams: distinct()

Puzzle

Streams: count()

Test

ArrayList

Test

Datos de referencia

Test

Interfaces funcionales

Puzzle

Métodos básicos de la clase String

Test

Instalación

Test

Funciones

Código

Estructuras de control

Código

Herencia de clases

Código

Streams: map()

Puzzle

Funciones y encapsulamiento

Test

Streams: match

Test

Gestión de errores y excepciones

Código

Datos primitivos

Puzzle

Todas las lecciones de Java

Accede a todas las lecciones de Java y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A Javascript

JavaScript

Sintaxis

Tipos De Datos

JavaScript

Sintaxis

Variables

JavaScript

Sintaxis

Operadores

JavaScript

Sintaxis

Estructuras De Control

JavaScript

Sintaxis

Funciones

JavaScript

Sintaxis

Funciones Cierre (Closure)

JavaScript

Sintaxis

Funciones Flecha

JavaScript

Programación Funcional

Filtrado Con Filter() Y Find()

JavaScript

Programación Funcional

Transformación Con Map()

JavaScript

Programación Funcional

Reducción Con Reduce()

JavaScript

Programación Funcional

Clases Y Objetos

JavaScript

Programación Orientada A Objetos

Excepciones

JavaScript

Programación Orientada A Objetos

Encapsulación

JavaScript

Programación Orientada A Objetos

Herencia

JavaScript

Programación Orientada A Objetos

Polimorfismo

JavaScript

Programación Orientada A Objetos

Array

JavaScript

Estructuras De Datos

Conjuntos Con Set

JavaScript

Estructuras De Datos

Mapas Con Map

JavaScript

Estructuras De Datos

Manipulación Dom

JavaScript

Dom

Selección De Elementos Dom

JavaScript

Dom

Modificación De Elementos Dom

JavaScript

Dom

Eventos Del Dom

JavaScript

Dom

Callbacks

JavaScript

Programación Asíncrona

Promises

JavaScript

Programación Asíncrona

Async / Await

JavaScript

Programación Asíncrona

Instalación De Java

Introducción Y Entorno

Configuración De Entorno Java

Introducción Y Entorno

Ecosistema Jakarta Ee De Java

Introducción Y Entorno

Tipos De Datos

Sintaxis

Variables

Sintaxis

Operadores

Sintaxis

Estructuras De Control

Sintaxis

Funciones

Sintaxis

Excepciones

Programación Orientada A Objetos

Clases Y Objetos

Programación Orientada A Objetos

Encapsulación

Programación Orientada A Objetos

Herencia

Programación Orientada A Objetos

Clases Abstractas

Programación Orientada A Objetos

Interfaces

Programación Orientada A Objetos

Sobrecarga De Métodos

Programación Orientada A Objetos

Polimorfismo

Programación Orientada A Objetos

La Clase Scanner

Programación Orientada A Objetos

Métodos De La Clase String

Programación Orientada A Objetos

Listas

Framework Collections

Conjuntos

Framework Collections

Mapas

Framework Collections

Funciones Lambda

Programación Funcional

Interfaz Funcional Consumer

Programación Funcional

Interfaz Funcional Predicate

Programación Funcional

Interfaz Funcional Supplier

Programación Funcional

Interfaz Funcional Function

Programación Funcional

Métodos Referenciados

Programación Funcional

Creación De Streams

Programación Funcional

Operaciones Intermedias Con Streams: Map()

Programación Funcional

Operaciones Intermedias Con Streams: Filter()

Programación Funcional

Operaciones Intermedias Con Streams: Distinct()

Programación Funcional

Operaciones Finales Con Streams: Collect()

Programación Funcional

Operaciones Finales Con Streams: Min Max

Programación Funcional

Operaciones Intermedias Con Streams: Flatmap()

Programación Funcional

Operaciones Intermedias Con Streams: Sorted()

Programación Funcional

Operaciones Finales Con Streams: Reduce()

Programación Funcional

Operaciones Finales Con Streams: Foreach()

Programación Funcional

Operaciones Finales Con Streams: Count()

Programación Funcional

Operaciones Finales Con Streams: Match

Programación Funcional

Api Optional

Programación Funcional

Api Java.nio 2

Entrada Y Salida (Io)

Api Java.time

Api Java.time

Accede GRATIS a Java y certifícate

Certificados de superación de Java

Supera todos los ejercicios de programación del curso de Java y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.

En esta lección

Objetivos de aprendizaje de esta lección

  1. Comprender el concepto de herencia en la programación orientada a objetos.
  2. Aprender a usar la palabra clave extends para heredar de una superclase en Java.
  3. Entender la jerarquía de clases en Java, y cómo todas las clases heredan de Object.
  4. Aprender cómo los modificadores de acceso (private, public, protected) afectan a la herencia.
  5. Aprender a usar la palabra clave super para acceder a miembros de la superclase en la subclase.
  6. Comprender cómo los constructores de la superclase son llamados durante la creación de un objeto de subclase.
  7. Entender cómo la herencia facilita el polimorfismo en Java.
  8. Conocer las limitaciones de la herencia en Java, incluyendo la falta de herencia múltiple.