Generics en Java

Antes de Java 5 cuando introducíamos objetos en una colección estos se guardaban como objetos de tipo Object, aprovechando el polimorfismo para poder introducir cualquier tipo de objeto en la colección. Esto nos obligaba a hacer un casting al tipo original al obtener los elementos de la colección.

import java.util.ArrayList;
import java.util.List;

public class Ejemplo {
  public static void main(String[] args) {
    List lista = new ArrayList();
    lista.add("Hola mundo");

    String cadena = (String) lista.get(0);
    System.out.println(cadena);
  }
}

Esta forma de trabajar no solo nos ocasiona tener que escribir más código innecesariamente, sino que es propenso a errores porque carecemos de un sistema de comprobación de tipos. Si introdujeramos un objeto de tipo incorrecto el programa compilaría pero lanzaría una excepción en tiempo de ejecución al intentar convertir el objeto en String:

import java.util.ArrayList;
import java.util.List;

public class Ejemplo {
  public static void main(String[] args) {
    List lista = new ArrayList();
    lista.add(22);

    String cadena = (String) lista.get(0);
    System.out.println(cadena);
  }
}

Podríamos utilizar el operador instanceof para comprobar el tipo del objeto antes de hacer el casting o antes de introducirlo en la colección, pero es poco elegante, añade aún más código a escribir, y no nos evitaría tener que hacer el casting.

Desde Java 5 contamos con una característica llamada generics que puede solventar esta clase de problemas. Los generics son una mejora al sistema de tipos que nos permite programar abstrayéndonos de los tipos de datos, de forma parecida a los templates o plantillas de C++ (pero mejor).

Gracias a los generics podemos especificar el tipo de objeto que introduciremos en la colección, de forma que el compilador conozca el tipo de objeto que vamos a utilizar, evitándonos así el casting. Además, gracias a esta información, el compilador podrá comprobar el tipo de los objetos que introducimos, y lanzar un error en tiempo de compilación si se intenta introducir un objeto de un tipo incompatible, en lugar de que se produzca una excepción en tiempo de ejecución.

Para utilizar generics con nuestras colecciones tan solo tenemos que indicar el tipo entre < y > a la hora de crearla. A estas clases a las que podemos pasar un tipo como “parámetro” se les llama clases parametrizadas, clases genéricas, definiciones genéricas o simplemente genéricas (generics). Veamos un ejemplo con la colección List.

import java.util.ArrayList;
import java.util.List;

public class Ejemplo {
  public static void main(String[] args) {
    List<String> lista = new ArrayList<String>();
    lista.add("Hola mundo");

    String cadena = lista.get(0);
    System.out.println(cadena);
  }
}

Este código, sin embargo, no compilaría, dado que hemos indicado que nuestra colección contendrá objetos de tipo String, y no Integer:

import java.util.ArrayList;
import java.util.List;

public class Ejemplo {
  public static void main(String[] args) {
    List<String> lista = new ArrayList<String>();
    lista.add(new Integer(22));

    Integer numero = lista.get(0);
    System.out.println(numero);
  }
}

Algo a tener en cuenta es que el tipo parámetro debe ser una clase; no podemos utilizar tipos primitivos. Esto, sin embargo, no es ningún problema gracias a las características de autoboxing y unboxing de Java 5. En el código siguiente, por ejemplo, se crea un objeto Integer intermedio de forma transparente, que es el objeto que se introducirá en realidad en la colección, y no un int de valor 22, como podría parecer. Posteriormente el objeto Integer se vuelve a convertir en int automáticamente.

import java.util.ArrayList;
import java.util.List;

public class Ejemplo {
  public static void main(String[] args) {
    List<Integer> lista = new ArrayList<Integer>();
    lista.add(22);

    int numero = lista.get(0);
    System.out.println(numero);
  }
}

Ahora, ¿cómo podemos crear nuestras propias clases parametrizadas? ¿cómo funciona a partir de Java 5 una colección como List? Sencillo. Solo tenemos que añadir el tipo parámetro después del nombre de la clase, y utilizar el nombre de este tipo genérico donde usaríamos un tipo concreto (con algunas excepciones). Por convención se suele utilizar una sola letra mayúscula para el tipo genérico.

Como ejemplo vamos a crear una pequeña clase que almacene un objeto, con su getter y setter, y que permita imprimir la cadena que lo representa, o bien imprimir la cadena al revés:

public class Imprimidor<T> {
  private T objeto;

  public Imprimidor(T objeto) {
    this.objeto = objeto;
  }

  public void setObjeto(T objeto) {
    this.objeto = objeto;
  }

  public T getObjeto() {
    return objeto;
  }

  public void imprimir() {
    System.out.println(objeto.toString());
  }

  public void imprimir_reves() {
    StringBuffer sb = new StringBuffer(objeto.toString());
    System.out.println(sb.reverse());
  }
}

Ahora podríamos utilizar nuestra nueva clase de la misma forma que hacíamos con List:

public class Ejemplo {
  public static void main(String[] args) {
    Imprimidor<String> impStr = new Imprimidor<String>("Hola mundo");
    impStr.imprimir();

    Imprimidor<Integer> impInt = new Imprimidor<Integer>(143);
    impInt.imprimir_reves();
  }
}

En ocasiones también puede interesarnos limitar los tipos con los que se puede parametrizar nuestra clase. Por ejemplo, podríamos querer crear una clase con un método que imprimiera el resultado de dividir un número entre 2. No tendría sentido permitir que se pudiera parametrizar la clase con el tipo String; nos interesaría pues encontrar alguna forma de indicar que el tipo utilizado debe ser un subtipo de la clase Number. Nuestro código podría tener un aspecto similar al siguiente:

public class Divisor<T extends Number> {
  private T numero;

  public Divisor(T numero) {
    this.numero = numero;
  }

  public void dividir() {
    System.out.println(numero.doubleValue() / 2);
  }
}

Ahora, en Java no existe la herencia múltiple, pero lo que si podemos hacer es implementar varias interfaces distintas. Si quisieramos obligar que el tipo implemente varias interfaces distintas, o que extienda una clase e implemente una o varias interfaces, tendríamos que separar estos tipos con el caracter &:

public class Divisor<T extends A & B> {
  ...
}

Otra cosa que podemos hacer es utilizar más de un parámetro de tipo, separando los tipos con comas. Supongamos por ejemplo que quisieramos crear una clase que imprimiera la suma de dos números. Escribiríamos algo como esto:

public class Sumador<T1 extends Number, T2 extends Number> {
  private T1 numero1;
  private T2 numero2;

  public Sumador(T1 numero1, T2 numero2) {
    this.numero1 = numero1;
    this.numero2 = numero2;
  }

  public void sumar() {
    System.out.println(numero1.doubleValue() + numero2.doubleValue());
  }
}

Por supuesto también podemos crear clases abstractas e interfaces de forma similar a las clases genéricas:

public interface MiColeccion<T> {
  public void anyadir(T objeto);
  public T obtener();
  public void ordenar();
}

E incluso métodos, en cuyo caso se indica el nombre a utilizar para el tipo genérico antes del valor de retorno del método. Sustituyamos como ejemplo la clase que imprimía las cadenas al revés por un método estático:

public class EjemploGenerics {
  public static <T> void imprimir_reves(T objeto) {
    StringBuffer sb = new StringBuffer(objeto.toString());
    System.out.println(sb.reverse());
  }
}

A la hora de llamar al método podemos especificar el tipo de la misma forma que hacíamos con las clases genéricas, aunque en este caso es menos útil:

public class EjemploGenerics {
  public static <T> void imprimir_reves(T objeto) {
    StringBuffer sb = new StringBuffer(objeto.toString());
    System.out.println(sb.reverse());
  }

  public static void main(String args[]) {
    String cadena = new String("Hola");

    EjemploGenerics ej = new EjemploGenerics();
    ej.imprimir_reves(cadena);
  }
}

También podemos omitirlo, por supuesto:

public class EjemploGenerics {
  public static <T> void imprimir_reves(T objeto) {
    StringBuffer sb = new StringBuffer(objeto.toString());
    System.out.println(sb.reverse());
  }

  public static void main(String args[]) {
    String cadena = new String("Hola");
    Integer entero = new Integer(12);
    Float flotante = new Float(13.6);
    Object objeto = new Object();

    imprimir_reves(cadena);
    imprimir_reves(entero);
    imprimir_reves(flotante);
    imprimir_reves(objeto);
  }
}


53 pensamientos en “Generics en Java”

  1. Ostias, justamente ahora estoy estudiando Java y estoy con el tema de los castings, me ha venido que ni pintado este post.

    PD: Buena explicación, pero eso si, te habrás quedado a gusto después del tocho…

  2. ¿El término “Generics” fue introducido por Java o por Microsoft en su suite .NET?

    Yo en java lo he conocido siempre como clases parametrizables.

    Quizas sea yo el equivocado, pero siempre evité llamarlo como Generics.

    En fin, un comentario nomás.

    1. el termino generico viene desde haskell, un lenguaje funcional donde su polimorfismo viene gracias al uso de genericos (no tiene oop)…la idea la han adaptado luego otros lenguajes empiricos, primero c# y mas reciente java

  3. Por desgracia, algo que podría estar bien, lo hicieron mal. Los genéricos no son nada más que un poco de sintactic sugar en el compilador. Es decir, el propio compilador está escribiendo los castings por tí, lo cual es cómodo pero no gran cosa.

    Un ejemplo de por qué los genéricos son solo la mitad de lo que deberían ser. Intentad hacer lo siguiente: “Crear un array de ArrayList”.

    Los genéricos son una chapucilla, de hecho, si escribes extensiones para Java usando JNI, no tienes forma de comprobar que tu código nativo está generando objetos válidos en Java cuando usas clases genéricas.

    Como suele pasar en java, buena idea, mal llevada a cabo. Una pena porque podría haber sido un buen cambio para el lenguaje.

    Saludos.

    1. el problema es que los genericos no están implementados a nivel de la jvm (como lo estan en c#)…asi que simplemente al compilar a bitcode la maquina virtual los borra en un proceso llamado type erasure…los genericos te permiten evitar errores en tiempo de compilacion…en vez de encontrar estos errores en el runtime..cosa que es peor, aun asi es una buena incorporacion…

  4. De programar no sé mucho, pero como usuario… Los programas hechos en java me resultan pesadísimos y lentísimos en el sistema, independientemente de que sean grandes o pequeños.

  5. Pues hombre, quizás te resulte curioso, pero tengo tu web en el Google Reader desde hace bastante tiempo y cuando posteabas algo de Java me chocaba bastante, me tiraba para atrás la verdad, como cuando te topas con una interfaz tipo photoshop o blender por primera vez.

    Pero poco a poco vas entendiendo la sintaxis, que quizás sea un poco especial, o me lo parece a mi, y la verdad que le estoy cogiendo el ritmo y lo que es la POO la estoy aprendiendo con Java y me estoy motivando bastante por decirlo de algún modo.

    Lo que si que me gustó fue el tutorial de python, era un lenguaje que no conocía en absoluto y que me parece genial, el obligar al programador a tabular bien el código es perfecto porque personalmente me gusta usar ese tipo de identación y no código “comprimido”.

    Saludos y perdón por el tocho.

  6. @Cane la verdad es que yo soy un enamorado de Python, y si fuera por mí todo lo hacía con Python 😛

    Pero bueno, cada lenguaje tiene su lugar y sus pros y sus contras.

  7. Me a parecido mas que perfecto tu post, muy bien explicado, siempre habia tenido esa duda de por que en codigos veia List<String> lista = new ArrayList<String>(); por ejemplo no sabia por que ponian ahora gracias a tu explicacion lo he entendido

    Gracias, y sigue trabajando en esta fabulosa pagina

  8. Algún detalle no demasiado importante:

    Cuando dices que el tipo parámetro debe ser un objeto, creo que sería más correcto decir que tiene que ser una clase.

    El código del tercer ejemplo no es incorrecto, pero es algo redundante, no es necesario hacer el casting porque ya has especificado que tu lista contiene elementos de la clase String.

    Por otra parte el ejemplo número 5 no compilará porque no se puede hacer un casting de Integer a String.

    Le doy la razón a Fernando sobre que no son todo lo que podrían/deberían ser, pero, y teniendo en cuenta temas de compatibilidad hacia atrás, la posibilidad de usar generics es una mejora sustancial respecto a los viejos tiempos.

    @Fernando, no sé si te estaré entendiendo mal cuando dices Crear un array de ArrayList, pero te refieres a algo como a esto:

    List lista = Arrays.asList(“ejemplo”, “de”, “array”, “desde”, “una”, “lista”);
    String [] l = (String[]) lista.toArray();

    Un saludo, y gracias por escribir tan a menudo.

  9. Cuando dices que el tipo parámetro debe ser un objeto, creo que sería más correcto decir que tiene que ser una clase.

    Cierto, claro. Fallo tonto.

    El código del tercer ejemplo no es incorrecto, pero es algo redundante, no es necesario hacer el casting porque ya has especificado que tu lista contiene elementos de la clase String.

    ¡Ouch! Hice copy paste del ejemplo pre-generics y se me pasó borrarlo 😛

    Por otra parte el ejemplo número 5 no compilará porque no se puede hacer un casting de Integer a String.

    Me ha pasado exactamente lo mismo. Modifiqué lo relevante del ejemplo y me olvidé del resto del código…

    En fin, muchas gracias. Lo corrijo 🙂

  10. Ostias tienes toda la razon sobre eso de las clases pero la verdad es ke solo eso del polimorfismo es incorrecto en el mundo de java solo se puede en ASSEMBLER…………..
    pero igual y sirve para lo L3IMS pero da igual ke sufran los k no saben

    THX…………………………
    IN SEARCH FOR THE PERFECTION……..
    AT THE END OF TIMES………………

  11. Excelente post.
    habia visto las
    pero no habia utilizado, tu entrada me ayuda bastante. buen ejemplo… ya no me duele tanto la cabeza saber que significaba esto.

    Saludos y exitos, desde Ecuador

  12. Excelente muchas gracias… andaba buscando que significaban esos simbolitos .. Gracias 🙂

    por cierto en NET creo que se usa el “of” no?

    dim ejem as new Ejemplo(of String)()

    :] , Saludos

  13. Con lo del ‘array de ArrayList’ que mencionaba @Fernando, me comentaron lo siguiente:
    Usando la clase java.lang.reflect.Array se puede escribir

    ArrayList[] arrayListArray[] = (ArrayList[])Array.newInstance(ArrayList.class,10);

    Saludos,

  14. Excelente, me lo explicaron en clase, pero con esto ya lo comprendi aun mas, segui adelante, para “poder seguir ayudando a gente que esta aprendiendo”, gracias por compartir tus conocimientos, que Dios te bendiga!!!

  15. hola, buena explicación pero tengo una duda.

    Yo estaba tratando de constestar una pregunta de una trabajo escrito: “¿Cuál es la importancia de los códigos genéricos en java?”

    ¿Se trata de lo mismo? Solo uso Java en este semestre y no creo que lo vuelva a usar por estos tiempos.

    Gracias.

  16. Excelente mini tutorial de una de las caracteristicas “novedosas” de java, es tema de certificacion si la deseas obtener por lo que es importante.
    Muy util esto en lo que se refiere a las collections

  17. estoy haciendo una classe Bibliotca que se compone de una classe Fitxa que a si vez tiene clases derivadas.
    que la tengo que implemetar de 2 formas
    Con un vector i com una Array
    con la interficie Comprable
    el vector ja lo tengo
    el problema veiene con la Array
    que classe tengo que utilizar
    arrays, array o arraylist
    xq la unica que tiene constructor es ArrayList
    xo quando utilizo el constructor de ArrayList
    me da error de
    compilacion :generic array creation
    i me da error a todas la funciones que manipulan en array add, remove,…

  18. El que los genericos en java no estan muy bien hechos lo demuestra este codigo:

    private void fun ()
    {
    List a = new ArrayList ();
    List b = a;
    a.add (“Hola”);
    b.add (new Integer(3));
    String s1 = a.get(0);
    Integer n2 = (Integer) b.get(1);
    String s2 = a.get(1);
    }

    que solo da error de ejecucion en la ultima linea

    1. para empezar eso no es un Generics, no estableces el tipo de objeto a recibir, y además para que querrias una lista de integers y string, los array son para agrupar datos logicamente relacionados y del mismo tipo.

      Tenemos que respetar las convenciones!!!

    1. Cada cuál tiene sus ventajas. Ocurre lo mismo que con las referencias vs. los punteros, por ejemplo. Yo, en mi caso, prefiero los generics aunque cuenten con algunas limitaciones, sí, que provienen de la necesidad de mantener la compatibilidad hacia atrás, no de que los ingenieros de Sun sean unos chapuzas. 😉

      Interesante tu blog, por cierto 🙂

      1. ¡Gracias! Llevo poco tiempo en la blogosfera, y se agradecen ese tipo de comentarios.

        Siguiendo con el hilo de la cuestión, el problema de Java son las decisiones de diseño. Si lo comparas con C#, por ejemplo, puedes ver que éste último incluye muchas funcionalidades (genéricos por ejemplo) desde su inicio, cosa que en java han tardado mucho en hacer, y lo han hecho mal. Los generics de C#, aunque no sean tan potentes como los templates de C++, sí están bien implementados, y no son un parche al lenguaje, como los de java.

        Échale un vistazo a ésto:
        Why Java Sucks & C# Rocks

        Ningún lenguaje es perfecto, en todos hay cosas buenas y malas. Yo no digo que C++ sea el lenguaje ideal. De hecho tengo muchas quejas sobre el mismo, parte de las cuales se solventarán con C++0x. Pero lo de java con los generics es un fallo gordo.

  19. Lo que veo en tu ejemplo de Imprimidor es que en el constructor estas agregando una instancia para el tipo parametrizado, twngo el siguiente caso:

    public class Hija extends Imprimidor {

    }

    public class Main {

    public static void main(String[] args) {
    Hija hija = new Hija();
    hija.imprimir()
    }

    }

    Esto manda un nullpointer ya que no existe la instacia para el objeto.

    Podria agregar un objeto String al constructor de hija para que funcione. Aqui el problema que yo tengo es que la instancia de Hija es por Spring. En este caso Hija es un bean de Spring.

    Como puedo saber dentro de Imprimidor cual es el tipo de T para generar la instancia de manera automatica y no tener un NullPointerException

  20. Pingback: Genericos en Java | Sólo para Mí!

  21. Pingback: Collection « delprogramador

  22. bro me podrias ayudar en un programa q me piden hacer una clase generica donde realize suma, resta multiplicacion y division????, me urge ayuda

  23. Muy util la explicacion de los generics en java. Andaba buscando una forma de pintar un arbol con icerfaces utilizando entityBeans mapeados a defaultTreeNode. tu explicacion me aclara el panorama para hacer un Nodo generico que reciba cualquier entity.

    Respeto a lo que discuten.. . NO se casen con un lenguaje. .no sean fanboys. un lenguaje es util solo cuando es util. .eso es muy relativo y va deacuerdo a cada caso. sin embargo java muestra todo su poder en el escenario web y con su version EE, aun asi no quiere decir que para apliacoines empresariales siempre deba usarse java.

  24. Hola

    Al llamar a un método quiero recibir un objeto de la clase que envio como parámetro, algo así:

    public T crearObjeto(Class clase) {

    }

    y lo invocaria mas o menos asi:

    MiClase.crearObjeto(Persona.class)

    ¿cómo podría hacerlo? porque tal cual lo he puesto no compila 🙁

    Gracias.

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.