La Programación Orientada a Objetos (POO) es el paradigma de programación más utilizado en la actualidad. La Herencia es uno de los 4 pilares de la programación orientada a objetos (POO) junto con la Abstracción, Encapsulación y Polimorfismo. Al principio cuesta un poco entender estos conceptos característicos del paradigma de la POO, pero la complejidad está en entender este nuevo paradigma y no en otra cosa.
La herencia es un mecanismo que permite la definición de una clase a partir de la definición de otra ya existente. La herencia permite compartir automáticamente métodos y datos entre clases, subclases y objetos. Esta característica permite la reutilización del código. En la herencia se establece una relación padre e hijo. Este mecanismo proporciona una forma rápida y cómoda de extender la funcionalidad de una clase.
En una jerarquía de clases, la clase en la parte superior del árbol se conoce como clase base o superclase, mientras que las clases debajo de ella se denominan subclases o clases derivadas. Las clases que están en la parte inferior en la jerarquía se dice que heredan de las clases que están en la parte superior en la jerarquía. El término heredar significa que las subclases disponen de todos los métodos y propiedades de su superclase.
Declaración de una Subclase
Cuando se declara una subclase, esa subclase hereda, en principio, todos los atributos y métodos de la superclase (clase padre). La sintaxis de la forma general de una declaración de clase que hereda una superclase es la siguiente:
class nombre-subclase extends nombre-superclase { //Cuerpo de la clase }
Lea también: SAT y Contabilidad Electrónica
Java soporta la herencia al permitir que una clase incorpore a otra en su declaración. Esto se hace mediante el uso de la palabra reservada extends. Así, la subclase añade (extiende) la superclase. La palabra reservada extends indica a la clase hija cuál va a ser su clase padre. Por ejemplo, al poner public class Futbolista extends SeleccionFutbol le estamos indicando a la clase Futbolista que su clase padre es la clase SeleccionFutbol, haciendo un "copy-paste dinámico" de todos los atributos y métodos públicos o protegidos de la superclase.
En Java cada clase solo puede tener una superclase o clase base, a lo que llamamos herencia simple. Solo es posible especificar una superclase para cualquier subclase que se cree, ya que Java no soporta la herencia de varias superclases en una sola subclase. Sin embargo, es posible crear una jerarquía de herencia en la que una subclase se convierte en la superclase de otra subclase. Por supuesto, ninguna clase puede ser superclase de sí misma.
Modificadores de Clase Relacionados con la Herencia
Clases Abstractas
Las clases abstractas no pueden ser instanciadas; sirven únicamente para declarar subclases que deben redefinir aquellos métodos que han sido declarados abstract. Las clases abstractas se crean para ser superclases de otras clases. Cuando alguno de los métodos de una clase es declarado abstracto, la clase debe ser obligatoriamente abstracta, de lo contrario, el compilador genera un mensaje de error. Esto no quiere decir que todos los métodos de una clase abstracta deban ser abstractos; incluso es posible que ninguno de ellos lo sea.
En este caso, la clase Animal es abstracta (no puede instanciarse), solo puede utilizarse como superclase de otras (Perro y Gallo). En este ejemplo, se ha declarado el método habla() como abstracto porque queremos que todos los animales puedan hablar, pero no sabemos qué es lo que van a decir (qué acciones se van a realizar), por lo que es declarada de tipo abstract.
Clases Finales
Una clase declarada final impide que pueda ser superclase de otras clases. Dicho de otra forma, ninguna clase puede heredar de una clase final. Esto es importante cuando se crean clases que acceden a recursos del sistema operativo o realizan operaciones de seguridad en el sistema.
Lea también: Validador CFDI 3.3: Todo lo que necesitas saber
Miembros de una Subclase: Atributos y Métodos
Atributos en la Herencia
Al heredar de una clase base, la clase derivada adquiere todos los miembros y métodos de la clase base, excepto aquellos que están marcados como private. Es muy importante decir que las clases hijas no van a heredar nunca los atributos y métodos privados de la clase padre, así que mucho cuidado con esto.
Modificadores de Acceso para Atributos y Métodos
Los modificadores de ámbito de atributo y método especifican la forma en que puede accederse a los mismos desde otras clases. Cuando la variable de una instancia se declara como private, restringimos su uso no autorizado o modificación. La herencia de clase no sobrescribe la restricción de acceso de private. Por lo tanto, aunque una subclase incluye a todos los miembros de su superclase, esta no puede acceder a los miembros de la superclase que se han declarado como private. Concluimos entonces que una variable debe ser private cuando queremos restringir modificaciones desde alguna subclase que descienda de la superclase.
Los modificadores de acceso determinan la visibilidad de los miembros de una clase:
- public: Es el más permisivo de todos. Un atributo o método public será visible en cualquier clase que desee acceder a él.
- protected: Permite el acceso dentro del mismo paquete y por subclases, incluso si están en diferentes paquetes. Por ejemplo, si una clase pertenece al mismo paquete que la clase Protegida, se puede acceder a sus atributos protected. Sin embargo, si la clase se declara en un paquete distinto, no se puede acceder a sus atributos protected directamente, a menos que sea una subclase.
- default (sin modificador): Permite el acceso a nivel de clases internas y paquetes. Los atributos que no llevan ningún modificador de ámbito pueden ser accedidos desde las clases del mismo paquete, pero no desde otros paquetes.
- private: Es el más restrictivo de todos. Todo atributo o método private es visible únicamente dentro de la clase en la que se declara. Solo los métodos de la clase donde se declara pueden acceder a ellos.
Métodos en la Herencia
Cuando se declara una subclase, esa subclase hereda, en principio, todos los atributos y métodos de la superclase. Estos métodos pueden ser redefinidos en la clase hija simplemente declarando métodos con los mismos identificadores, parámetros y tipo devuelto que los de la superclase. El método reemplazado por una declaración override se conoce como método base reemplazado. El método base reemplazado debe tener la misma firma que el método override.
Los métodos de una clase que se declaran de tipo final no pueden ser redefinidos por las subclases. A diferencia del modificador abstract, pueden existir en la clase métodos final sin que la clase que los contiene sea final (solo se protegen algunos métodos de la clase que no pueden ser redefinidos). Los métodos abstract se declaran en las clases abstract. Cuando se declara un método abstract, no se implementa el cuerpo del método, solo su signatura. Las clases que se declaran como subclases de una clase abstract deben implementar los métodos abstract.
Lea también: pasos para comprobar la autenticidad de un CFDI
Constructores y Herencia
Los constructores son independientes de cada clase; superclase o subclase, cada uno cuenta con sus propios constructores ya que una superclase no conoce a los elementos de la subclase y no tiene acceso a ellos. Sin embargo, una subclase sí puede tener acceso al constructor de una superclase.
Cuando la subclase y la superclase definen constructores, el proceso se vuelve más complicado porque ambos constructores deben ejecutarse. En este caso, Java utiliza la palabra reservada super que funciona de dos formas generales. La primera llama a un constructor de la superclase, la segunda se usa para que un miembro de una subclase acceda a un miembro de la superclase que se ha ocultado.
Una subclase puede llamar a un constructor definido por la superclase con el uso de la siguiente instrucción super(lista-parámetros). Cuando decimos lista-parámetros, especificamos cualquier parámetro que el constructor de la superclase necesita. super() debe ser siempre la primera instrucción que se ejecute dentro de un constructor de subclase. Cuando una subclase llama a super(), está llamando al constructor de su superclase inmediata. Por lo tanto, super() siempre hace referencia a la superclase inmediatamente superior a la clase que llama, aunque existan varios niveles de jerarquías. Además, esta instrucción siempre debe ser la primera instrucción ejecutada dentro de un constructor de subclase.
Es bastante habitual, cuando se sobrescribe el constructor o constructores de la superclase, realizar una llamada al constructor de la superclase y después realizar otras operaciones de inicialización de la clase hija. Esto se realiza utilizando el identificador de clase super.
Polimorfismo en el Contexto de la Herencia
La herencia es fundamental para el polimorfismo. A través de la herencia, una clase puede utilizarse como más de un tipo; puede utilizarse como su propio tipo, o como el tipo de su clase base. Por ejemplo, si la clase Perro y la clase Gato heredan de la clase Animal, podemos hacer que Perro y Gato se comporten como tales o como animales (clase base). Esto permite trabajar con colecciones de objetos de diferentes tipos, tratándolos de manera uniforme como objetos de su superclase, y que los métodos correctos se invoquen según el tipo real del objeto en tiempo de ejecución.
Si se declara un ArrayList de objetos SeleccionFutbol (clase padre) y se añaden objetos de sus subclases (Futbolista, Entrenador, Masajista), se puede recorrer el ArrayList y ejecutar sus métodos "comunes" (como Concentrarse y Viajar). Esto es posible ya que todos los objetos son hijos de la misma clase padre.
Interfaces para Superar la Herencia Múltiple
La limitación de que solo se puede heredar de una clase hace que haya problemas, ya que muchas veces se deseará heredar de varias clases. Mediante interfaces se definen una serie de comportamientos de objeto. Estos comportamientos pueden ser "implementados" en una determinada clase. No definen el tipo de objeto que es, sino lo que puede hacer (sus capacidades).
Para hacer que una clase utilice una interfaz, se añade detrás del nombre de la clase la palabra implements seguida del nombre del interfaz. Se pueden poner varios nombres de interfaces separados por comas, solucionando, en cierto modo, el problema de la herencia múltiple en Java.
