dimecres, 2 de març del 2016

Clases genéricas en Java

Con la clase ArrayList hemos podido comprobar cómo podíamos hacer una lista de cualquier objeto, pues esta clase utiliza este paradigma de java, que se llama programación genérica.
La programación genérica consiste en hacer un código que puedas reutilizar para objetos de diversos tipos, y ahí está la clave, la reutilización del código. De esta manera evitas crear varias clases concretas para cada uno de los objetos. De tal forma, con ArrayList podemos guardar cualquier tipo de objeto.
En la clase ArrayList modificamos el parámetro de tipo, por ejemplo <String>.
Se le denomina genérica porque estás haciendo una programación de una clase que maneja objetos en general.
Podríamos hacer lo mismo por herencia, con una única clase que sea capaz de manejar objetos de diferente tipo. Ya sabemos que en Java tenemos la clase Object de la que heredan las demás, utilizando estas podríamos llegar a usar clases de cualquier tipo pero trae varios inconvenientes:
  • Uso continuo del casting.
  • Complicación del código.
  • No tienes la posibilidad de comprobar errores de ejecución.
En cambio, la programación genérica tiene:
  • Mayor sencillez del código.
  • Reutilización del código en numerosos escenarios.
  • Comprobación de errores en tiempo de compilación.
La programación genérica no existía hasta la versión 5.0 de Java. Si tuviésemos que hacer esto sin clases genéricas tendríamos algunos problemas...
Podemos ver un ejemplo de cómo sería una clase ArrayList antes de la versión 5.0 y sus desventajas.


package ArrayList;

public class ArrayList {
private Object[] datosElemento;
private int i=0;
public ArrayList(int z){
    datosElemento = new Object[z];
}

ArrayList() {
    throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
   
public Object get(int i){
    return datosElemento[i];
}
   
public void add(Object o){
    datosElemento[i]=o;
    i++;
}
}
Un ejemplo del uso de esta sería el siguiente.


package ArrayList;

import java.io.*;

public class ArrayList_Ejemplo {
public static void main(String[] args){
    ArrayList archivos = new ArrayList(6);
    archivos.add("Pepe");
    archivos.add("Maria");
    archivos.add("Juanito");
    archivos.add("Jesus");
    archivos.add("Fran");
    archivos.add(new File("gestion.ves"));
  
    String nombre = (String) archivos.get(2); //Inconveniente del casting
    System.out.println(nombre);
    //Las siguientes lineas no marcarán errores pero petará en tiempo de ejecución, esto no pasa con la programación generica.
    //String problema = (String) archivos.get(5); //Inconveniente del casting
    //System.out.println("Problema : " + problema);
  
    ArrayList files = new ArrayList(5);
    files.add(new File("gestion_Pedidos.bla"));
    File archivo = (File) files.get(0);
    System.out.println(archivo);
}
}


Crear una clase genérica propia

Para verlo, será mejor ver un ejemplo.
Si tenemos la clase genérica creada por nosotros, llamada Pareja, donde T es un objeto cualquiera que será definido en la instanciación de la clase, igual que pasaba con los ArrayList. Como podemos observar, en vez de poner String o un tipo, siempre pondremos T, de esta manera cuando pongamos entre <> el nombre del tipo de objeto que tendrá que almacenar, todo pasará a ser de ese tipo y de esta manera podemos adaptarlo para cualquier situación.
Por ejemplo, Pareja quedaría asi.
package ClaseGenerica;
public class Pareja<T> {
private T primero;
public Pareja(){
      primero=null;
}
public void setPrimero(T nuevoValor){
      primero = nuevoValor;
}
public T getPrimero(){
    return primero;
}
}

Y podemos observar unos ejemplos:
package ClaseGenerica;

public class Pareja_Ejemplo {
public static void main(String[] args){
    Pareja<String> ejemploString = new Pareja<>();
    ejemploString.setPrimero("prueba");
    System.out.println(ejemploString.getPrimero());
    Pareja<Persona> ejemploPersona = new Pareja<>();
    Persona persEjemplo = new Persona("fmesasc");
    ejemploPersona.setPrimero(persEjemplo);
    System.out.println(ejemploPersona.getPrimero());
                              //La clase generica se adapta al elemento.
}
}

class Persona{
public Persona(String nombre){
    this.nombre = nombre;
}
@Override
public String toString(){
    return nombre;
}
private String nombre;
}
De esta manera podemos observar, con cualquier IDE como NetBeans, que una vez instanciada Pareja<String>, todos los valores que eran T pasarán a ser <String> hasta en las sugerencias.

Métodos genéricos

Un método genérico sería lo mismo que una clase genérica pero en método. Pueden estar dentro de clases ordinarias o clases genéricas. Podemos verlo con el siguiente ejemplo.
public class MetodosGenericos {
public static void main(String[] args){
    String nombres[]={"Pepe","Mónica","Francisco"};
    System.out.println(MisMatrices.getElementos(nombres));
    //Si queremos el elemento menor:
    System.out.println(MisMatrices.getMenor(nombres));
}
}
class MisMatrices{
public static <T> String getElementos(T[] a){
    return "El array tiene: " + a.length + " elementos";
}
//Todos los objetos tendran que tener implementado Comparable, sino dará error.
public static <T extends Comparable> T getMenor(T[] aux){
    if(aux==null || aux.length==0){
        return null;
    }
    T elementoMenor = aux[0];
    for(int i=1;i<aux.length;i++){
        if(elementoMenor.compareTo(aux[i])>0){
            elementoMenor=aux[i];
        }
    }
    return elementoMenor;
}
}

Herencia y tipos comodín


El tema de la herencia no funciona igual en las clases genéricas que en las ordinarias. Por ejemplo, una clase Jefe puede heredar de empleado y usar la sustitución, si podemos decir que un jefe siempre es un empleado, jefe hereda de empleado, pero un empleado no tiene porque ser un jefe. Pues este principio de sustitución no funciona en las clases genéricas. Si tenemos una clase genérica de empleado y de jefe no podemos relacionarlos en las clases genéricas. Entonces tendríamos que hacer un método que reciba un argumento con un genérico de tipo empleado, entonces aquí dentro podemos usarlo mediante el extends del padre de la herencia, algo así como Pareja<? extends Empleado> p, sería el parámetro que se le pasa, de esta manera puede ser empleado o jefe directamente.

Cap comentari :

Publica un comentari a l'entrada