HERENCIA La herencia es uno de los mecanismos de los lenguajes de programación orientada a objetos basados en clases, por medio del cual una clase se deriva de otra de manera que extiende su funcionalidad. La clase de la que se hereda se suele denominar clase base, clase padre, superclase, clase ancestro (el vocabulario que se utiliza suele depender en gran medida del lenguaje de programación). En los lenguajes que cuentan con un sistema de tipos fuerte y estrictamente restrictivo con el tipo de datos de las variables, la herencia suele ser un requisito fundamental para poder emplear el Polimorfismo, al igual que un mecanismo que permita decidir en tiempo de ejecución qué método debe invocarse en respuesta a la recepción de un mensaje, conocido como enlace tardío o enlace dinámico. Ejemplo en Java import javax.*; import javax.swing.JOptionPane; public class Mamifero{ private int patas; private String nombre; public void imprimirPatas(){ JOptionPane.showMessageDialog(null," Tiene " + patas + " patas\n", "Mamifero", JOptionPane.INFORMATION_MESSAGE); } public Mamifero(String nombre, int patas){ this.nombre = nombre; this.patas = patas; } } public class Perro extends Mamifero { public Perro(String nombre){ super(nombre, 4); } } public class Gato extends Mamifero { public Gato(String nombre){ super(nombre, 4); } } public class CrearPerro { public static void main(String[] args) { Perro perrito = new Perro("Pantaleon"); perrito.imprimirPatas(); /*Está en la clase mamífero*/ } } Se declaran las clases mamíferos, gato y perro, haciendo que gato y perro sean unos mamíferos (derivados de esta clase), y se ve como a través de ellos se nombra al animal pero así también se accede a patas dándole el valor por defecto para esa especie. Es importante destacar tres cosas. La primera, es que la herencia no es un mecanismo esencial en el paradigma de programación orientada a objetos; en la mayoría de los lenguajes orientados a objetos basados en prototipos las clases no existen, en consecuencia tampoco existe la herencia y el polimorfismo se logra por otros medios. La segunda, es que el medio preferido para lograr los objetivos de extensibilidad y reutilización es la agregación o composición. La tercera, es que en lenguajes con un sistema de tipos débiles, el polimorfismo se puede lograr sin utilizar la herencia. Por otra parte y aunque la herencia no es un concepto indispensable en el paradigma de programación orientada a objetos, es mucho más que un mecanismo de los lenguajes basados en clases, porque implica una forma de razonar sobre cómo diseñar ciertas partes de un programa. Es decir, no sólo es un mecanismo que permite implementar un diseño, sino que establece un marco conceptual que permite razonar sobre cómo crear ese diseño. PROTECTED Hasta ahora habíamos dicho que una subclase no tiene acceso a los campos de una superclase de acuerdo con el principio de ocultación de la información. Sin embargo, esto podría considerarse como demasiado restrictivo. Decimos que podría considerarse demasiado restrictivo porque limita el acceso a una subclase como si se tratara de una clase cualquiera, cuando en realidad la relación de una superclase con una subclase es más estrecha que con una clase externa. Por ello en diferentes lenguajes, Java entre ellos, se usa un nivel de acceso intermedio que no es ni public ni private, sino algo intermedio que se denomina como “acceso protegido”, expresado con la palabra clave protected, que significa que las subclases sí pueden tener acceso al campo o método. El modificador de acceso protected puede aplicarse a todos los miembros de una clase, es decir, tanto a campos como a métodos o constructores. En el caso de métodos o constructores protegidos, estos serán visibles/utilizables por las subclases y otras clases del mismo package. El acceso protegido suele aplicarse a métodos o constructores, pero preferiblemente no a campos, para evitar debilitar el encapsulamiento. En ocasiones puntuales sí resulta de interés declarar campos con acceso protegido. La sintaxis para emplear esta palabra clave es análoga a la que usamos con las palabras public y private, con la salvedad de que protected suele usarse cuando se trabaja con herencia. Desde un objeto de una subclase podremos acceder o invocar un campo o método declarado como protected, pero no podemos acceder o invocar a campos o métodos privados de una superclase. Declara un campo de una clase como protected y en un test crea un objeto de la subclase y trata de acceder a ese campo con una invocación directa del tipo interino43.IdProfesor = “54-DY-87”. Java admite una variante más en cuanto a modificadores de acceso: la omisión del mismo (no declarar ninguno de los modificadores public, private o protected). En la siguiente tabla puedes comparar los efectos de usar uno u otro tipo de declaración en cuanto a visibilidad de los campos o métodos: MODIFICADOR CLASE PACKAGE SUBCLASE TODOS public Sí Sí Sí Sí protected Sí Sí Sí No No especificado Sí Sí No No private Sí No No No EJEMPLOS Considera que estás desarrollando un programa Java donde trabajas con la superclase Profesor y la subclase Profesor Emerito. Crea el código para estas clases que cumpla los requisitos que indicamos. Como atributos de la superclase tendremos nombre (String), edad (int) y años Consolidados (int) declarados como protected. En la subclase se trabajará con el campo adicional años Emerito declarado como private. Un método de la subclase será double obtener Salario Base () que obtendrá el salario base como (925 + años Consolidados * 33.25 + 47.80 * años Emerito). Intenta acceder directamente al campo años Consolidados desde la subclase (como si fuera un campo más de la subclase) para implementar este método. Es posible sin utilizar una invocación a super ni un método get que ocurre si el atributo en la superclase lo declaras prívate. SUPER En apartados anteriores del curso hemos trabajando con herencia, polimorfismo y sobre escritura de métodos. Hemos usado como clases de ejemplo las clases Persona, Profesor, Profesor Interino y Profesor Titular, donde Profesor hereda de Persona y los profesores interino y titular de Profesor. ¿Cómo imprimir un listado con todos los profesores (titulares e interinos) y todos sus datos? En los métodos de Profesor solo tenemos disponibles los campos de Profesor, pero no los campos de Profesor Titular o de Profesor Interino. A su vez, Profesor Titular (o Profesor Interino) no conoce los campos de Profesor porque son privados, y no queremos hacerlos públicos para no romper el encapsulamiento (principio de ocultación) de la clase. Hemos visto cómo en los constructores de subclases usábamos la sintaxis súper () en la primera línea para invocar al constructor de la superclase. Veremos ahora que esta otra sintaxis: súper. Nombre Del Método (parámetros si los hay); tiene como efecto la invocación de un método de la superclase. Consideremos el ejemplo anterior. En el método mostrar Datos() de la clase Profesor Interino escribimos lo siguiente: EJEMPLO: public void mostrarDatos() { super.mostrarDatos(); System.out.println("Comienzo interinidad: FechaComienzoInterinidad.getTime().toString() ); " + } ¿Qué hemos hecho? Dentro del método “hijo” hemos incluido una llamada al método “padre” que había perdido visibilidad debido a que lo habíamos sobre escrito. Al ejecutar el programa ahora obtenemos: Se procede a mostrar los datos de los profesores existentes en el listín Datos Profesor. Profesor de nombre: Juan Hernández García con Id de profesor: Prof 22-387-11 Datos Profesor. Profesor de nombre: José Luis Morales Pérez con Id de profesor: Unknown Comienzo interinidad: Fri Nov 22 12:40:38 CET 2019 Gracias a la instrucción súper. Mostrar Datos () hemos incluido dentro de un método sobre escrito el método deseado de la superclase. La llamada a súper dentro de un método puede ocurrir en cualquier lugar dentro del código. Equivale a la ejecución del método de la superclase y es opcional: la hacemos sólo cuando nos resulta necesaria o conveniente para poder acceder a un método sobre escrito. Considera que estás desarrollando un programa Java donde trabajas con la superclase Profesor y la subclase Profesor Emérito. Crea el código para estas clases que cumpla los requisitos que indicamos. Como atributos de la superclase tendremos nombre (String), edad (int) y años Consolidados (int). Un método de la superclase será double obtener Salario Base () que obtendrá el salario base como (725 + el número de años consolidados multiplicado por 33.25). En la subclase se trabajará con el campo adicional años Emérito. Queremos hacer lo siguiente: sobre escribir el método obtener Salario Base () en la subclase para calcular el salario base del profesor emérito invocando mediante la palabra clave súper al cálculo del salario base de Profesor y añadiéndole la cantidad de (47.80 * años Emérito). FINAL En los programas que generemos usualmente intervendrán constantes: valores matemáticos como el número Pi, o valores propios de programa que nunca cambian. Si nunca cambian, lo adecuado será declararlos como constantes en lugar de cómo variables. Supongamos que queremos usar una constante como el número Pi y que usamos esta declaración: EJEMPLOS // Ejemplo aprenderaprogramar.com public class Calculadora { private double PI = 3.1416; public void mostrarConstantePi () { System.out.println (PI); } … constructor, métodos, … código de la clase … } Si creas un objeto de tipo Calculadora, comprobarás que puedes invocar el método mostrar Constante Pi para que te muestre el valor por pantalla. No obstante, una declaración de este tipo presenta varios problemas. En primer lugar, lo declarado no funciona realmente como constante, sino como variable con un valor inicial. Prueba a establecer this.PI = 22; dentro del método y verás que es posible, porque lo declarado es una variable, no una constante. En segundo lugar, cada vez que creamos un objeto de tipo calculadora estamos usando un espacio de memoria para almacenar el valor 3.1416. Así, si tuviéramos diez objetos calculadora, tendríamos diez espacios de memoria ocupados con la misma información, lo cual resulta ineficiente. Para resolver estos problemas, podemos declarar constantes en Java usando esta sintaxis: CaracterPublico/Privado valorDeLaConstante; static final TipoDeLaConstante = En esta declaración intervienen dos palabras clave cuyo significado es importante: a) static: los atributos miembros de una clase pueden ser atributos de clase o atributos de instancia; se dice que son atributos de clase si se usa la palabra clave static: en ese caso la variable es única para todas las instancias (objetos) de la clase (ocupa un único lugar en memoria). A veces a las variables de clase se les llama variables estáticas. Si no se usa static, el sistema crea un lugar nuevo para esa variable con cada instancia (la variable es diferente para cada objeto). En el caso de una constante no tiene sentido crear un nuevo lugar de memoria por cada objeto de una clase que se cree. Por ello es adecuado el uso de la palabra clave static. Cuando usamos “static final” se dice que creamos una constante de clase, un atributo común a todos los objetos de esa clase. b) final: en este contexto indica que una variable es de tipo constante: no admitirá cambios después de su declaración y asignación de valor. final determina que un atributo no puede ser sobre escrito o redefinido. O sea: no funcionará como una variable “tradicional”, sino como una constante. Toda constante declarada con final ha de ser inicializada en el mismo momento de declararla. final también se usa como palabra clave en otro contexto: una clase final (final) es aquella que no puede tener clases que la hereden. Lo veremos más adelante cuando hablemos sobre herencia. Cuando se declaran constantes es muy frecuente que los programadores usen letras mayúsculas (como práctica habitual que permite una mayor claridad en el código), aunque no es obligatorio. Una declaración en cabecera de una clase como private final double PI = 3.1416; podríamos interpretarla como una constante de objeto. Cada objeto tendrá su espacio de memoria con un contenido invariable. Una declaración en cabecera de una clase como private static final double Pi = 3.1416; la interpretamos como una constante de clase. Existe un único espacio de memoria, compartido por todos los objetos de la clase, que contiene un valor invariable. Veamos ejemplos de uso: // Ejemplo aprenderaprogramar.com // Sintaxis: CarácterPublico/Privado valorDeLaConstante; static final TipoDeLaConstante private static final float PI = 3.1416f; //Recordar f indica que se trata de un float private static double PI = 3.1416; public static final String passwd = "jkl342lagg"; public static final int PRECIO_DEFAULT = 100; Cuando usamos la palabra clave static la declaración de la constante ha de realizarse obligatoriamente en cabecera de la clase, junto a los campos (debajo de la signatura de clase). Es decir, un atributo de clase hemos de declararlo en cabecera de clase. Si tratamos de incorporarlo en un método obtendremos un error. Por tanto dentro del método main (que es un método de clase al llevar incorporado static en su declaración) no podemos declarar constantes de clase. Por otro lado, final sí puede ser usado dentro de métodos y también dentro de un método main. Por ejemplo una declaración como final String psswd = “mt34rsm8” es válida dentro de un método. En resumen: en cabecera de clase usaremos static final para definir aquellas variables comunes a todos los objetos de una clase. Modifica el código de la clase Calculadora que vimos anteriormente para declarar PI como una constante de clase. Luego, intenta modificar el valor de PI usando una invocación como this.PI =22;. Comprobarás que se produce un error al compilar ya que los valores de las constantes no son modificables. Define una clase Java denominada Circulo que tenga como atributo de clase (estático) y constante numeroPi, siendo esta constante de tipo double y valor 3.1416. Además la clase tendrá el atributo radio (tipo double) que representa el radio del círculo, y los métodos para obtener y establecer los atributos. También debe disponer de un método para calcular el área del círculo (método tipo funcion area Circulo que devuelve el área) = y la longitud del círculo (método tipo función que devuelve la longitud). Busca información sobre las fórmulas necesarias para crear estos métodos en internet si no las recuerdas. En una clase con el método main, declara el código que cree un objeto círculo, le pida al usuario el radio y le devuelva el área y la longitud del círculo. ABSTRACT Una clase abstracta es aquella que posee al menos un método abstracto. Un método abstracto es aquél que está declarado, pero que no posee implementación alguna (es decir no tiene cuerpo). Está incompleto. Debido a esta carencia, es imposible declarar objetos de una clase abstracta. ¿Para qué sirve entonces una clase abstracta? Una clase abstracta sirve para establecer un marco de trabajo que deben cumplir todas las clases que hereden de ella. Las clases que heredan de ella sí deben dotar de una implementación a los métodos abstractos del padre. Si no lo hacen, automáticamente se convierten también en abstractas. También es posible definir una clase abstracta sin que posea métodos abstractos. Las clases y métodos abstractos llevan la palabra clave abstract. Es importante que tengas en cuenta que no pueden llevar el modificador abstract: los constructores los métodos estáticos los métodos privados Ejemplo 1. La clase abstracta Mascota declara un método abstracto hablar que deberá ser redefinido por las subclases Gato, Perro y Pato. Observa que la clase Mascota solo se está utilizando para declarar el método hablar, de modo que las clases hijas la puedan usar. /** * Abstract class Mascota - Representa una mascota */ public abstract class Mascota { /** * Método abstracto hablar para las mascotas */ abstract public void hablar(); } //fin de la clase Mascota /** * La clase Gato que hereda de la clase Mascota. */ public class Gato extends Mascota { /** * Implementación del método hablar */ public void hablar() { System.out.println( "Meow" ); } // fin del método hablar } // fin de la clase Gato /** * La clase Perro que hereda de la clase Mascota. */ public class Perro extends Mascota { /** * Implementación del método hablar */ public void hablar() { System.out.println( "Woof" ); } // fin del método hablar } // fin de la clase Perro /** * La clase Pato que hereda de la clase Mascota. */ public class Pato extends Mascota { /** * Implementación del método hablar */ public void hablar() { System.out.println( "Quack" ); } // fin del método hablar } // fin de la clase Pato /** * Clase controladora para los objetos Gato, Perro, Pato. * */ public class Granja { /** * Método principal de la aplicación */ public static void main( String[] args ) { Mascota[] misMascotas = new Mascota[4]; misMascotas[0] = new Pato(); misMascotas[1] = new Perro(); misMascotas[2] = new Gato(); misMascotas[3] = new Pato(); for( int i = 0; i < misMascotas.length; i++ ) misMascotas[i].hablar(); } } //fin del método main //fin de la clase Granja Ejemplo 2. La clase abstracta Producto incluye un método abstracto imprimirDatos. Observa el uso de la palabra clave abstract antes del nombre de la clase a fin de informar al compilador de Java que la clase incluye un método abstracto, o más. En virtud de que la clase abstracta no define todos sus métodos no se pueden crear instancias de la clase Producto. /** * Abstract class Producto - Representa el producto de una tienda * */ public abstract class Producto { // variables de instancia protected double precioVenta; protected double costoFabrica; protected String nombreProducto; /** * Constructor para Producto * * @param nombre El nombre del producto * @param precioVenta El precio de venta del producto * @param costoFabrica El costo de fábrica del producto */ public Producto( String nombre, double precio, double costo ) { nombreProducto = nombre; precioVenta = precio; costoFabrica = costo; } // fin del constructor Producto /** * Método abstracto para imprimirDatos */ public abstract String imprimirDatos(); } // fin de la clase Producto Observa que las clases Libro y DVD que extienden de la clase abstracta Producto e implementan el método abstracto imprimirDatos. /** * Clase Libro que hereda de la clase Producto. */ public class Libro extends Producto { /** * Constructor para los objetos de la clase Libro * @param titulo El título del libro * @param precio El precio de venta del libro * @param costo El costo de fábrica del libro */ public Libro( String titulo, double precio, double costo ) { super( titulo, precio, costo ); } /** * Imprime los datos asociados con el libro * * @return los datos del libro en un String */ public String imprimirDatos() { return "Libro: " + nombreProducto + "\tPrecio: $" + precioVenta; } // fin del método imprimirDatos } // fin de la clase Libro /** * Clase Libro que hereda de la clase Producto. */ public class DVD extends Producto { /** * Constructor para los objetos de la clase DVD * @param titulo El título del DVD * @param precio El precio de venta del DVD * @param costo El costo de fábrica del DVD */ public DVD( String titulo, double precio, double costo ) { super( titulo, precio, costo ); } /** * Imprime los datos asociados con el DVD * * @return los datos del DVD en un String */ public String imprimirDatos() { return "DVD: " + nombreProducto + "\tPrecio: $" + precioVenta; } //fin del método imprimirDatos } //fin de la clase DVD /** * Clase controladora para los objetos de DVD y Libros. */ public class PruebaProducto { /** * Método principal de la aplicación */ public static void main( String[] args ) { Libro miLibro = new Libro( "Biblioteca del programador", 54.95, 39.95 ); DVD miDVD = new DVD( "Curso multimedia de Java", 29.95, 19.95 ); } } System.out.println( "Los datos de mis productos." ); System.out.println( miLibro.imprimirDatos() ); System.out.println( miDVD.imprimirDatos() ); //fin del método main //fin de la clase PruebaProducto