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ícateLa 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.
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
Streams: reduce()
Streams: flatMap()
Llamada y sobrecarga de funciones
Métodos referenciados
Métodos de la clase String
Representación de Fecha
Operadores lógicos
Tipos de datos
Estructuras de iteración
Streams: forEach()
Objetos
Funciones lambda
Uso de Scanner
CRUD en Java de modelo Customer sobre un ArrayList
Tipos de variables
Streams: collect()
Operadores aritméticos
Interfaz funcional Consumer
API java.nio 2
API Optional
Interfaz funcional Function
Encapsulación
Interfaces
Uso de API Optional
Representación de Hora
Herencia básica
Clases y objetos
Interfaz funcional Supplier
HashMap
Sobrecarga de métodos
Polimorfismo de tiempo de ejecución
OOP en Java
Creación de Streams
Streams: min max
Métodos avanzados de la clase String
Polimorfismo de tiempo de compilación
Excepciones
Herencia avanzada
Estructuras de selección
Uso de interfaces
HashSet
Objeto Scanner
Streams: filter()
Operaciones de Streams
Interfaz funcional Predicate
Streams: sorted()
Configuración de entorno
CRUD en Java de modelo Customer sobre un HashMap
Uso de variables
Clases
Streams: distinct()
Streams: count()
ArrayList
Datos de referencia
Interfaces funcionales
Métodos básicos de la clase String
Instalación
Funciones
Estructuras de control
Herencia de clases
Streams: map()
Funciones y encapsulamiento
Streams: match
Gestión de errores y excepciones
Datos primitivos
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
Sintaxis
Tipos De Datos
Sintaxis
Variables
Sintaxis
Operadores
Sintaxis
Estructuras De Control
Sintaxis
Funciones
Sintaxis
Funciones Cierre (Closure)
Sintaxis
Funciones Flecha
Programación Funcional
Filtrado Con Filter() Y Find()
Programación Funcional
Transformación Con Map()
Programación Funcional
Reducción Con Reduce()
Programación Funcional
Clases Y Objetos
Programación Orientada A Objetos
Excepciones
Programación Orientada A Objetos
Encapsulación
Programación Orientada A Objetos
Herencia
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
Array
Estructuras De Datos
Conjuntos Con Set
Estructuras De Datos
Mapas Con Map
Estructuras De Datos
Manipulación Dom
Dom
Selección De Elementos Dom
Dom
Modificación De Elementos Dom
Dom
Eventos Del Dom
Dom
Callbacks
Programación Asíncrona
Promises
Programación Asíncrona
Async / Await
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
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
- Comprender el concepto de herencia en la programación orientada a objetos.
- Aprender a usar la palabra clave
extends
para heredar de una superclase en Java. - Entender la jerarquía de clases en Java, y cómo todas las clases heredan de
Object
. - Aprender cómo los modificadores de acceso (
private
,public
,protected
) afectan a la herencia. - Aprender a usar la palabra clave
super
para acceder a miembros de la superclase en la subclase. - Comprender cómo los constructores de la superclase son llamados durante la creación de un objeto de subclase.
- Entender cómo la herencia facilita el polimorfismo en Java.
- Conocer las limitaciones de la herencia en Java, incluyendo la falta de herencia múltiple.