Listado completo de tutoriales

69 - Clases genéricas


Java permite crear clases que administren distintos tipos de datos.

Se utilizan mucho para la administración de colecciones de datos (pilas, colas, listas, árboles etc.)

Las clases genéticas nos evitan duplicar clases que administran tipos de datos distintos pero implementan algoritmos similares, son una herramienta fundamental para reutilizar código.

Vimos en conceptos anteriores como implementar estructuras dinámicas de tipo pila y dependiendo del problema definíamos una pila de enteros o de String o de cualquier otro tipo de datos.

Si no existiera el concepto de genéricos en Java deberíamos implementar una clase por cada tipo de dato que administra una pila.

Problema

Implementar una clase Pila que administre cualquier tipo de datos mediante el concepto de genéricos.

Crear luego 4 objetos de la clase Pila y almacenar en la primer pila objetos de la clase Persona, en la segunda objetos de la clase Carta, en la tercera objetos de la clase String y finalmente en la cuarta objetos de la clase Integer.

Programa:

public class Persona {
    private String nombre;
    private int edad;

    public Persona(String nombre, int edad) {
        this.nombre = nombre;
        this.edad = edad;
    }

    public void imprimir() {
        System.out.println(nombre + " " + edad);
    }

}
public class Carta {
    private int numero;
    private String palo;

    Carta(int numero, String palo) {
        this.numero = numero;
        this.palo = palo;
    }

    public void imprimir() {
        System.out.println(numero + " " + palo);
    }

}
public class Pila<E> {
    class Nodo {
        public E info;
        public Nodo sig;
    }

    private Nodo raiz;

    public void insertar(E x) {
        Nodo nuevo;
        nuevo = new Nodo();
        nuevo.info = x;
        if (raiz == null) {
            nuevo.sig = null;
            raiz = nuevo;
        } else {
            nuevo.sig = raiz;
            raiz = nuevo;
        }
    }

    public E extraer() {
        if (raiz != null) {
            E informacion = raiz.info;
            raiz = raiz.sig;
            return informacion;
        } else {
            return null;
        }
    }

    public int cantidad() {
        int cant = 0;
        Nodo reco = raiz;
        while (reco != null) {
            reco = reco.sig;
            cant++;
        }
        return cant;
    }

}

public class PruebaGenericos {
    public static void main(String[] args) {
        Pila<Persona> pila1 = new Pila<Persona>();
        pila1.insertar(new Persona("Juan", 33));
        pila1.insertar(new Persona("Ana", 45));
        pila1.insertar(new Persona("Carlos", 33));
        System.out.println("Extraemos el primer elemento de la pila de personas:");
        Persona ultimaPersona = pila1.extraer();
        ultimaPersona.imprimir();

        Pila<Carta> pila2 = new Pila<Carta>();
        pila2.insertar(new Carta(1, "Corazón"));
        pila2.insertar(new Carta(2, "Corazón"));
        pila2.insertar(new Carta(1, "Trebol"));
        pila2.insertar(new Carta(2, "Trebol"));
        System.out.println("Extraemos el primer elemento de la pila de cartas:");
        Carta ultimaCarta = pila2.extraer();
        ultimaCarta.imprimir();

        Pila<String> pila3 = new Pila<String>();
        pila3.insertar("cadena 1");
        pila3.insertar("cadena 2");
        pila3.insertar("cadena 3");
        pila3.insertar("cadena 4");
        System.out.println("Extraemos el primer elemento de la pila de String:");
        String ultimoString = pila3.extraer();
        System.out.println(ultimoString);

        Pila<Integer> pila4 = new Pila<Integer>();
        pila4.insertar(1);
        pila4.insertar(2);
        pila4.insertar(3);
        pila4.insertar(4);
        System.out.println("Extraemos el primer elemento de la pila de Integer:");
        Integer entero = pila4.extraer();
        System.out.println(entero);

    }
}
}

Las clases Persona y Carta no tienen nada nuevo con respecto a temas vistos anteriormente.

Para plantear una clase genética debemos indicar luego del nombre de la clase entre los símbolos menor y mayor un nombre, por convención se utiliza el caracter 'E' (Element):

public class Pila<E> {

El nombre 'E' almacena el tipo de dato que administrará la pila y se lo inicializa cuando se crea un objeto de la clase Pila:

        Pila<Persona> pila1 = new Pila<Persona>();
        ...
        Pila<Carta> pila2 = new Pila<Carta>();
        ...
        Pila<String> pila3 = new Pila<String>();
        ...
        Pila<Integer> pila4 = new Pila<Integer>();
        ...

Como vemos cuando creamos el objeto pila1 le antecedemos entre los símbolos menor y mayor el tipo de dato que insertaremos en la pila1, en nuestro caso indicamos la clase Persona.

De forma similar creamos los objetos pila2, pila3 y pila4 indicando en cada uno de los casos otros nombres de clases.

Un punto importante a tener en cuenta que en Java no podemos crear un objeto de una clase genérica de un tipo de dato primitivo, por ejemplo se genera un error de compilación si intentamos hacer:

        Pila<int> pila5 = new Pila<int>();

El algoritmo de la clase Pila no solo cambia en su declaración, sino en cada lugar que hacemos referencia al tipo genérico.

La información del nodo será del tipo genérico 'E':

    class Nodo {
        public E info;
        public Nodo sig;
    }

El método insertar recibe un parámetro x de tipo 'E', el algoritmo propiamente no varía:

    public void insertar(E x) {
        Nodo nuevo;
        nuevo = new Nodo();
        nuevo.info = x;
        if (raiz == null) {
            nuevo.sig = null;
            raiz = nuevo;
        } else {
            nuevo.sig = raiz;
            raiz = nuevo;
        }
    }

El método extraer retorna un tipo de dato genérico:

    public E extraer() {
        if (raiz != null) {
            E informacion = raiz.info;
            raiz = raiz.sig;
            return informacion;
        } else {
            return null;
        }
    }

Retornar