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);
  }
}


Comentarios
  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…

    Responder

  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.

    Responder

    • andrew

      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

      Responder

  3. Desde Sun también se refieren a ellos como generics, Luciano: J2SE 5.0 New Features: Generics

    Responder

  4. 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.

    Responder

    • @Fernando toda la razón. Pero bueno, es lo que hay.

      Responder

    • andrew

      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…

      Responder

  5. Telendro

    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.

    Responder

  6. 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.

    Responder

  7. @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.

    Responder

  8. @Zootropo

    Gracias por la aclaración acerca de la denominación Generics.

    Responder

  9. Asterion

    @Telendro
    Es por que estan mal hechos 🙂
    Saludos!

    Responder

  10. Luis

    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

    Responder

  11. Linkamp

    Muy bien explicado el articulo y muy útil. Felicidades

    Responder

  12. 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.

    Responder

  13. 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 🙂

    Responder

  14. Juanelo

    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………………

    Responder

  15. 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

    Responder

  16. Oscar

    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

    Responder

  17. Hector

    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,

    Responder

  18. 41fr3d1t0

    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!!!

    Responder

  19. Cralitos

    excelente post, me resumiste todo un capitulo de un libro que me hablaba de generics, lo entendi mejor aca

    Responder

  20. naty

    Genial!! lo que buscaba y re bien explicado!!!
    Gracias!!

    Responder

  21. 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.

    Responder

  22. jigsaw

    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

    Responder

  23. Sergi

    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,…

    Responder

  24. muchas gracias me sirvió demasiado..saludos desde Paraguay

    Responder

  25. Angel

    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

    Responder

    • Gallo

      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!!!

      Responder

    • zzzz

      y donde queres que te tire error sino?

      Responder

  26. Excelente aporte… !!!

    Responder

  27. Parece ser que no has tratado en profundidad los generics. Decir que son mejores que los templates de C++ es una barbaridad. Los templates son mucho más potentes y seguros, y no una chapuza barata como los generics de java.

    Te recomiendo echarle un vistazo a ésto:
    http://caymcorp.wordpress.com/2010/06/23/generics-en-java-java-sucks/

    Responder

    • 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 🙂

      Responder

      • ¡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.

        Responder

  28. Erick

    Que buena explicación.
    Ni en la catedra te lo explican asi de bien.
    Me sacaste de una duda Thanks.

    Responder

  29. Sergio

    Excelente explicación, quería dejarte el agradecimiento!

    Responder

  30. Excelente aporte! 🙂 la verdad es los Generics de java ayudan un friego! 🙂

    Responder

  31. Isra

    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

    Responder

  32. […] Este ejemplo es de un post del blog mundo geek: […]

    Responder

  33. adli

    hola muy bien, por fin entendi lo de clases genericas en java
    gracias

    Responder

  34. […] Para trabajar con colecciones en Java podemos hacer uso del framework Collections. Las clases e interfaces que componen este framework se encuentran en los paquetes java.util y java.util.concurrent. Todas hacen uso del polimorfismo paramétrico que proporciona generics; concepto que tratamos ampliamente en la entrada Generics en Java. […]

    Responder

  35. Marcelo

    Excelente artículo amigo.

    Responder

  36. jimmy

    Muchas gracias me aclaro el tema bastante, saludos desde Colombia

    Responder

  37. Emilio

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

    Responder

  38. Renato

    Muy Buen Post

    Responder

  39. Diego

    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.

    Responder

  40. Juan Pablo

    Excelente tutorial, se explica de una forma sencilla, pero completa.

    Responder

  41. Donnie Emanuel

    Gracias, estuvo muy instructivo.

    Responder

  42. Hola, muy bien explicado, no es facil entender el concepto, felicitaciones.

    Responder

  43. stefano

    gracias chicos ppor el aporteeeeEEEEE

    Responder

  44. forumisto

    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.

    Responder

Deja un comentario