En PC Resumen hablaremos de que son las Clases genéricas en Java. El concepto de una estructura de datos, como por ejemplo una pila, se puede entender independientemente del tipo de elemento que manipula. Las clases genéricas proporcionan los medios para describir el concepto de pila (o cualquier otra clase) en forma independiente de su tipo. Así podemos crear instancias de objetos con tipos específicos de la clase genérica. Esto nos permite una gran oportunidad de reutilizar el software.

Una vez que tenemos una clase genérica, podemos utilizar una notación concisa para indicar el tipo actual a utilizarse en lugar del parámetro de tipo de la clase. En tiempo de compilación, el compilador Java asegura la seguridad de los tipos de nuestro código y utiliza las técnicas de borrado para permitir que nuestro código interactúe con la clase genérica.

En el código siguiente vemos la declaración de una clase genérica Pila. La declaración de la clase es similar a la de una clase no genérica, salvo que el nombre de la clase va seguido de una sección de parámetros de tipo. En este caso, el parámetro de tipo E, que será el que manipulará la pila. Al igual que con los métodos genéricos, la sección de parámetros de tipo puede tener uno o más parámetros separados por comas. El parámetro de tipo E se utiliza en la declaración de la clase Pila para representar el tipo del elemento.

public class Pila <E> {
    private final int tamaño;
    private int superior;
    private E[] elementos;

    public Pila() {
        this (10); // tamaño predeterminado de la pila
    }

    public Pila(int s) {
        tamaño = s > 0 ? s : 10;
        superior = -1;
        elementos = (E[]) new Object[tamaño];
    }

    public void apilar(E valor) {
        if (superior == tamaño - 1) {
            throw new ExcepcionPilaLlena(String.format ("La Pila está llena, no se puede apilar %s", valor));
        }
        elementos [++ superior] = valor;
    }

    public E desapilar() {
        if (superior == -1) {
            throw new ExcepcionPilaVacia("La Pila está vacía, no se puede desapilar");
        }
        return elementos[superior--];
    }
}

La clase Pila declara la variable elementos como un array de tipo E. Quisiéramos crear un array de tipo E para guardar los elementos, pero el mecanismo de los genéricos no permite parámetros de tipo en las expresiones para crear arrays, dado a que el parámetro de tipo no está disponible en tiempo de ejecución. Para poder hacerlo se crea como un array de tipo Object y se convierte la referencia vuelta para new al tipo E[]. Cualquier objeto podría almacenarse en un array Object, pero el mecanismo de comprobación de tipos del compilador asegura que sólo puedan asignarse al array objetos del tipo declarado de la variable, a través de cualquier expresión de acceso que utilice la variable elementos.

Sin embargo, si compilamos con la opción Xlint: Unchecked (da información sobre todos los "warnings" de conversiones no comprobadas), el compilador nos sacará el siguiente mensaje:

warning: [Unchecked] Unchecked cast
found: java.lang.Object[]
required: E[]
elementos = (E[]) new Object[tamaño];

La razón de este mensaje es que el compilador no puede asegurar al 100% que esta mesa nunca contendrá objetos que no sean de tipo E. Por ejemplo, supongamos que E representa el tipo Integer. Es posible asignar la variable elementos a una variable de tipo Object []:

Object[] taulaObjects = elementos;
taulaObjects[0] = "hola";

Esto nos pondría una cadena en una tabla de enteros y nos podría daría problemas. Mientras no hacemos cosas como esta, la pila sólo tendrá objetos del tipo correcto.

Los métodos apilar y desapilar tiran las excepciones ExcepcioPilaLlena y ExcepcioPilaVacia que tenemos a continuación:

public class ExcepcioPilaLlena extends RuntimeException {
    
    public ExcepcioPilaLlena() {
        this ("La Pila está llena");
    }

    public ExcepcioPilaLlena(String excepcion) {
        super(excepción);
    }
}
public class ExcepcioPilaVacia extends RuntimeException {

    public ExcepcioPilaVacia() {
        this ("La Pila está vacía");
    }

    public ExcepcioPilaVacia(String excepcion) {
        super (excepción);
    }
}

El alcance de un parámetro de tipo de una clase genérica es toda la clase. De todas formas, los parámetros de tipo no se pueden utilizar en las declaraciones static de una clase.

A continuación tenemos una aplicación para poder probar la clase genérica. La aplicación está hecha para provocar todas las excepciones, las cuales son capturadas.

public class PruebaPila {
    private double[] elementsDouble = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6};
    private int[] elementsInteger = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
    private Pila<Double> pilaDouble;
    private Pila <Integer> pilaInteger;

    public void probarPilas() {
        pilaDouble = new Pila<Double>(5);
        pilaInteger = new Pila<Integer>(10);
        pruebaApilarDouble();
        pruebaDesapilarDouble();
        pruebaApilarInteger();
        pruebaDesapilarInteger();
    }

    public void pruebaApilarDouble() {
        try {
            System.out.println( "\nApilando elementos a pilaDouble");
            for (double elemento : elementsDouble) {
                System.out.printf("%.1f", elemento);
                pilaDouble.apilar(elemento);
            }
        } catch (ExcepcioPilaLlena excepcioPilaLlena) {
            System.err.println();
            excepcioPilaLlena.printStackTrace();
        }
    }

    public void pruebaDesapilarDouble() {
        try {
            System.out.println( "\n Desapilando elementos de pilaDouble");
            double valorAQuitar;
            while(true) {
                valorAQuitar = pilaDouble.desapilar();
                System.out.printf("%.1f", valorAQuitar);
            }
        } catch (ExcepcioPilaVacia excepcioPilaVacia) {
            System.err.println();
            excepcioPilaVacia.printStackTrace();
        }
    }

    public void pruebaApilarInteger() {
        try {
            System.out.println("\n Apilando elementos a pilaInteger");
            for (int elemento : elementsInteger) {
                System.out.printf("%d", elemento);
                pilaInteger.apilar(elemento);
            }
        } catch (ExcepcioPilaLlena excepcioPilaLlena) {
            System.err.println();
            excepcioPilaLlena.printStackTrace();
        }
    }

    public void pruebaDesapilarInteger() {
        try {
            System.out.println("\n Desapilando elementos de pilaInteger");
            int valorAQuitar;
            while(true) {
                valorAQuitar = pilaInteger.desapilar();
                System.out.printf("%d", valorAQuitar);
            }
        } catch (ExcepcioPilaVacia excepcioPilaVacia) {
            System.err.println();
            excepcioPilaVacia.printStackTrace();
        }
    }

    public static void main(String args[]) {
        PruebaPila aplicacion = new PruebaPila();
        aplicacion.probarPilas();
    }
}

Creación de genéricos para probar la clase Pila<E>

Si observamos los códigos de apilar y desapilar para la pila de Double y para la pila de Integer vemos que es prácticamente igual. Por lo tanto es una oportunidad para utilizar métodos genéricos. En el siguiente programa se declara el método genérico pruebaApilar para que realice las mismas tareas que los dos métodos anteriores. Lo mismo hace el método genérico pruebaDesapilar.

El método genérico pruebaApilar utiliza un parámetro de tipo T para representar el tipo de datos almacenado. El método genérico recibe tres argumentos: una cadena que representa el nombre del objeto, una referencia a un objeto de tipo Pila <T> y una tabla de tipo T. El método genérico pruebaDesapilar recibe dos argumentos: una cadena que representa el nombre de el objeto y una referencia a un objeto de tipo Pila <T>.

public class PruebaPila2 {
    private Double[] elementsDouble = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6};
    private Integer[] elementsInteger = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
    private Pila<Double> pilaDouble;
    private Pila<Integer> pilaInteger;

    public void probarPilas () {
        pilaDouble = new Pila<Double>(5);
        pilaInteger = new Pila<Integer>(10);
        probarApilar("pilaDouble", pilaDouble, elementsDouble);
        probarDesapilar("pilaDouble", pilaDouble);
        probarApilar("pilaInteger", pilaInteger, elementsInteger);
        probarDesapilar("pilaInteger", pilaInteger);
    }

    public <T> void probarApilar(String nombre, Pila<T> pila, T[] elementos) {
        try {
            System.out.printf ("\nApilando elementos en %s\n", nombre);
            for (T elemento: elementos) {
                System.out.printf ("%s", elemento);
                pila.apilar(elemento);
            }
        } catch (ExcepcioPilaLlena excepcioPilaLlena) {
            System.out.println();
            excepcioPilaLlena.printStackTrace ();
        }
    }

    public <T> void probarDesapilar(String nombre, Pila<T> pila) {
        try {
            System.out.printf ("\nDesapilando elementos de %s\n", nombre);
            T valorAQuitar;
            while (true) {
                valorAQuitar = pila.desapilar();
                System.out.printf("%s", valorAQuitar);
            }
        } catch (ExcepcioPilaVacia excepcioPilaVacia) {
            System.out.println();
            excepcioPilaVacia.printStackTrace();
        }
    }

    public static void main(String args[]) {
        PruebaPila2 aplicacion = new PruebaPila2();
        aplicacion.probarPilas();
    }
}
Pin It