Imprimir
Categoría: Java
Visto: 2378

En PC Resumen vamos a explicar la implementación de los genéricos en Java. Si las operaciones realizadas por los métodos sobrecargados son idénticas para cada tipo de argumento, los métodos sobrecargados pueden codificarse de forma más compacta y conveniente utilizando genéricos.

A continuación veremos un programa que nos imprime tres arrays diferentes.

public class PruebaMetodoGenerico {
    public static<E> void imprimirArray (E[] arrayEntrada) {
        for (E elemento: arrayEntrada) {
            System.out.printf ("% s", elemento);
        }
        System.out.println();
    }
    public static void main (String args[]) {
        Integer[] arrayInteger = {1, 2, 3, 4, 5, 6};
        Double[] arrayDouble = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7};
        Character[] arrayCharacter = {'H', 'O', 'L', 'A'};
        System.out.println("El array arrayInteger contiene:");
        imprimirArray(arrayInteger);
        System.out.println("El array arrayDouble contiene:");
        imprimirArray(arrayDouble);
        System.out.println("El array arrayCharacter contiene:");
        imprimirArray(arrayCharacter);
    }
}

Todas las declaraciones de métodos genéricos tienen una sección de parámetros de tipo delimitada por signos <i> que se ponen delante del tipo de valor de retorno del método. Cada sección de parámetros de tipo contiene uno o más parámetros de tipo, separados por comas. Un parámetro de tipo, también conocido como variable de tipo, es un identificador que especifica el nombre de un tipo genérico. Los parámetros de tipo pueden utilizarse para declarar el tipo de valor de retorno, los tipos de los parámetros y los tipos de las variables locales en la declaración de un método genérico. El cuerpo de un método genérico se declara como cualquier otro método. Los parámetros de tipo sólo pueden representar tipos por referencia y no tipos primitivos.

Cuando el compilador traduce el método genérico en códigos byte de Java, elimina la sección de parámetros de tipo y reemplaza los parámetros de tipo con tipos reales. A este proceso se le conoce como borrado de tipo (type Erasure).

Al declarar imprimirArray como método genérico, eliminamos la necesidad de los métodos sobrecargados ahorrando código y creando un método reutilizable que pueda imprimir las representaciones de cadena de los elementos de cualquier array que contenga objetos. En este caso realmente hubiera funcionado igual si tuviéramos un array de Object como parámetro ya que cualquier Object se puede imprimir como String. Más adelante veremos las ventajas de hacerlo con genéricos.

Consideremos ahora un ejemplo de método genérico, en el que se utilizan parámetros de tipo en el tipo de valor de retorno y en la lista de parámetros. La aplicación utiliza un método genérico llamado máximo para determinar y devolver el mayor de los tres argumentos del mismo tipo. Por desgracia no se puede utilizar el operador relacional '>' con los tipos de referencia. De todas formas, es posible comparar dos objetos de la misma clase, si esta clase implementa la interfaz genérica Comparable <T> (paquete java.lang). Todas las clases envoltorio de tipo para los tipos primitivos implementan esta interfaz. Al igual que las clases genéricas, las interfaces genéricas permiten a los programadores especificar, mediante la declaración de una sola interfaz, un conjunto de tipos relacionados. Los objetos Comparable <T> tienen un método llamado compareTo.

public class PruebaMaxim{
    public static<T extends Comparable <T>> T max(T x, T y, T z) {
        T max = x;
        if (y.compareTo (max)> 0) max = y;
        if (z.compareTo (max)> 0) max = z;
        return max;
    }

    public static void main(String args[]) {
        System.out.printf("Máximo de %d,%d y %d es %d \n \n", 3, 4, 5, máximo (3, 4, 5));
        System.out.printf ("Máximo de %.1f, %.1f y %.1f es %.1f \n \n",6.6, 8.8, 7.7, máximo (6.6, 8.8, 7.7));
        System.out.printf ("Máximo %s, %s y %s es %s \n", "pera", "Manzana", "naranja", máximo ( "pera", "manzana", "naranja"));
    }
}

Es responsabilidad del programador en definir una clase que implemente a Comparable<T> declarar el método compareTo, de tal forma que compare el contenido de dos objetos de esta clase y devuelva los resultados de esta comparación. El método debe devolver 0 si los objetos son iguales, -1 si objeto1 es menor que objeto2 y 1 si objeto1 es mayor que objeto2. Es interesante implementar esta interfaz ya que pueden utilizarse objetos Comparable con los métodos de ordenación y búsqueda que tienen las colecciones que veremos al siguiente tema.

El método genérico maximo utiliza el parámetro T como el tipo de valor de retorno, como el tipo de parámetros y como el tipo de la variable max. La sección de parámetros de tipo especifica que T extiende a Comparable<T> (sólo podrán utilizarse objetos que implementen esta interfaz). En este caso Comparable<T> se conoce como límite superior del parámetro de tipo. De forma predeterminada Object es el límite superior. Hay que ver que las declaraciones de los parámetros de tipo que delimitan el parámetro siempre utilizan la palabra extends sin importar que sea una clase o una interfaz.

Cuando el compilador traduce el método genérico utiliza el borrado para reemplazar los parámetros de tipo con tipos reales. Todos los parámetros de tipo se reemplazan con el límite superior del parámetro de tipo. Por lo tanto en este caso sólo podremos pasar objetos Comparable<T> como argumentos de la función, sino se producirá un error de compilación. El resultado de la traducción será el siguiente:

public static Comparable maximo (Comparable x, Comparable y, Comparable z) {
    Comparable max = x;
    if (y.compareTo (max)> 0) max = y;
    if (z.compareTo (max)> 0) max = z;
    return max;
}

Tras el borrado, la versión compilada especifica que devuelve el tipo Comparable. De todas formas el método que hace la llamada no espera recibir un objeto Comparable sino un objeto del mismo tipo que se ha pasado a la función como argumento. Cuando el compilador reemplaza la información del parámetro de tipo con el tipo del límite superior en la declaración del método también inserta operaciones de conversión explícitas ante cada llamada al método, para asegurar que el valor devolver sea del tipo esperado. Así por ejemplo la primera llamada a la función va precedida por una conversión a Integer:

(Integer) maxim (3,4,5);

Sin los genéricos nosotros seríamos responsables de implementar la operación de conversión. El uso de genéricos asegura que la conversión insertada nunca tire una excepción ClassCastException, asumiendo que utilizamos genéricos en nuestro código.