sábado, 31 de enero de 2009

Tipos Genéricos o Parametrizados en Java 5.0

Saludos y bienvenidos a mi segunda publicación de Acerca de Java. Como lo prometido es deuda, aquí discutiremos acerca de los Tipos Genéricos o Parametrizados.

Estos tipos genéricos o parametrizados (Generics) es una de las innovaciones más conocidas que promueve Java 5.0, estos nos permiten crear una clase que pueda operar con cualquier tipo de dato, pero este tipo no se especifica hasta que se instancie la clase. Por ello, la clase es genérica, de allí su nombre, también es llamada parametrizada por que el parámetro es especificado en la instanciación, es el parámetro de tipo formal.

Aunque puedo crear mis propias clases que trabajen con cualquier tipo de dato de referencia (Integer, Float, Double -- como se ve en el último ejemplo de este artículo --), la mayor utilidad que se les da a los generics es con las colecciones, ya que en ellas puedo definir con que tipo de dato trabajará la misma. Por ejemplo las clases ArrayList, Vector, HastTable y demás colecciones que en las versiones anteriores manejaban datos Object solamente, ahora podemos especificar que tipo de datos manejarán. Nuestros ejemplos usarán sólo ArrayList, sólo por utilizar alguna.

Supongamos que en la versión anterior de Java, tenemos el siguiente código:

//Codigo 1:
ArrayList strLista = new ArrayList();
ArrayList intLista = new ArrayList();

Podemos suponer por el nombre de los objetos ArrayList que el primero alvergará String y el segundo enteros, (OJO, sólo por el nombre lo suponemos) pero no es así, la verdad ambos objetos podrán trabajar con cualquier tipo de dato, ya que ArrayList trabaja con Object y este tipo de dato permite cualquier tipo de datos como Integer, String, Float o Double, etc...

Ahora veamos el siguiente código, utilizando Genéricos de java 5.0:

//Código 2:
ArrayList<String> strLista = new ArrayList<String>();
ArrayList<Integer> intLista = new ArrayList<Integer>();

Ahora si hemos especificado que strLista trabajará con cadenas de caracteres y intLista con enteros, sólo agregando entre corchetes el tipo de dato. El compilador arrojará un error si en alguna oportunidad intentamos asignar al objeto un dato que no cumpla con su norma, es decir, podremos hacer:

//Código 3:
strLista.add("Cadena 1 para strLista");
strLista.add("cadena 2 para strLista");

pero no:

//Código 4:
strLista.add(3);

Mientras que el compilador hubiera aceptado el código 4 si se hubiese creado strLista y intLista por el código 1.

A continuación las ventajas que esto trae:

* El compilador no aceptará que se agregue ningún tipo de dato distinto al especificado en la instanciación de la clase.
* No es necesario añadir los castings que eran indispensables para recuperar los datos homogéneos de una colección Object.
* Se mantiene un mayor control sobre la colección ya que si en la versión anterior la colección hubiese aceptado el código 4, al ejecutar el código 6 hubiese ocurrido un error en tiempo de ejecución que puede ser más difícil de detectar y/o manejar.

Veamos el siguiente código:

//Código 5: Lo correcto en generics


Iterator<String> strIterador = strLista.iterator();
while (strIterador.hasNext()) {
String dato = strIterador.next();
System.out.println(dato);
}



//Codigo 6: Lo que ahora no es necesario:


Iterator strIterador = strLista.iterator();
while(strIterador.hasNext()){
String dato = (String) strIterador.next();/*Este cast
ya no es necesario si se utiliza Generics*/
System.out.println(dato);
}

Aunque ambos códigos son válidos es mejor usar el código 5, por las ventajas que implica.

NOTAS IMPORTANTES:
Hay que tener en cuenta que no pueden crearse Generics con los tipos de datos primitivos como int, float, double. Si lo intentase el compilador arrojaría un error. También es necesario saber que no se permiten arreglos de Generics, el siguiente codigo no es permitido:

//Codigo 7

ArrayList<String> strLista[] = new ArrayList<String>[3];

Con el codigo 7 el compilador arrojará un error mencionando la no disponibilidad de arreglos de generics.

Acerca de los Comodines en Generics:
Para terminar sobre nuestro artículo dedicado a Generics, es necesario mencionar el tema de los comodines. En algunos momentos se quiere que una clase genérica se comporte se comporte como una super clase de otras clases genéricas, veamos el siguiente ejemplo explicativo:

//Codigo 8:


public class Comodin<E extends Number> {

E item;

public E getItem() {
return item;
}

public void setItem(E item) {
this.item = item;
}

public static void procesarNumero(Comodin<Number>
nbrComodin){
System.out.println("Procesando Number");
}

public static void procesarDouble(Comodin<Double>
dblComodin){
System.out.println("Procesando Doubles");
}

public static void procesarComodin(Comodin<?>
cmdComodin){
System.out.println("Procesando Comodin");
}

/*
* public static void procesarString(Comodin<String>
* strComodin){
*
* }
*/
public static void main(String[] args) {
Comodin<Number> nbrComodin = new Comodin<Number>();
Comodin<Double> dblComodin = new Comodin<Double>();
nbrComodin.setItem(new Double(5));
nbrComodin.setItem(new Float(3.3f));
// procesarNumero(dblComodin);
procesarNumero(nbrComodin);
procesarDouble(dblComodin);
// procesarDouble(nbrComodin);
procesarComodin(nbrComodin);
procesarComodin(dblComodin);
}
}


Es un código un poco más largo pero ejemplifica perfectamente lo que es posible con los comodines, se puede ver que la clase Comodín puede manejar cualquier tipo de dato numérico ya que se colocó Comodin<E extends Number> por ello podré realizar instancias como: Comodin<Number>, Comodin<Double> o por ejemplo puedo colocar el comodín y decir Comodin<?>, con ello estamos diciendo que en este caso podré manejar cualquier tipo de dato de referencia siempre y cuando sea Number o cualquier subtipo de ella. Por lo mismo, el método procesarString no dejará compilar el código si apareciera descomentado.

El comodín no puede utilizarse para crear instancias es sólo para métodos que reciben como parámetro una clase Genérica o para declarar clases genéricas que necesiten cualquier tipo de dato, podemos ver el ejemplo con procesarComodin en caso de métodos que reciben instancias de cualquier tipo o podemos hacer:


List<?> lista = new Arraylist<CualquierClase>();


Esto es equivalente a:

List lista = new Arraylist();


La diferencia está en que la segunda lanza una advertencia (Warning) ya que prefiere una especificación de parámetro genérico aunque sea comodín.

Además, se puede asignar a un objeto Comodin<Number> un valor Double o Integer debido a que son numéricos, esto se puede ver cuando se realiza nbrComodin.setItem(new Double(5)); , pero en el caso de que desee pasar a un método que recibe una clase genérica de Double, no puedo pasarle una Number, ni Integer, debe ser una clase Comodin<Double>, como lo indica la declaración del método, a menos que utilice el comodín "?" que especifica que puedo pasar cualquier tipo de clase numérica, por eso no puedo descomentar procesarNumero(dblComodin); ni procesarDouble(nbrComodin);, ya que no dejarían compilar el código.

Bueno, espero me haya explicado y haya servido este material para aclarar el tema de los generics de java 5.0, sin más, me despido hasta a próxima. Chao...

23 comentarios:

Anónimo dijo...

Gracias, me ha servido mucho tu explicación. Saludos!!

Kelvin dijo...

Me alegra saber que a alguien le ha servido esta información, hace que esto valga la pena

Anónimo dijo...

pues sí, muchas gracias, a mí también me ha servido para aclarar ciertas dudas que tenía

sigue así, un saludo

Anónimo dijo...

Hola: hice los cursos de Java (SL210/275) en la UTN (Argentina), y lo que se ve en estos no es ni la 1/3 parte de lo que se necesita saber para rendir la certificacion. Estoy tratando de entender todo el concepto de genericos, y por fin encontre una explicacion clara y concisa. Muchisimas gracias por tu aporte y segui asi.
Saludos.
Pablo.

davidgk dijo...

Coincido con ospaco. no entendia el tema de genericos.. y despues lo imagine para armar por ejemplo clases para los cruds implementando hibernate.. para los DAOS.. me volo la cabeza la idea.. y aparte muy claro como lo explicaste.. Muchas gracias por tu aporte
David

Unknown dijo...

Me parece una muy mala practica ya que dificulta poder seguir el codigo y en un proyecto de 5 clases no pasa nada, pero en un proyecto mas o menos mediano dificulta mas de lo que facilita.

Anónimo dijo...

Esta de lujo man

fernando dijo...

muy claro, pero una duda con respecto:

"NOTAS IMPORTANTES:"

decís que no puede crearse Generics con tipos primitivos, el ejemplo "código 7" me parece que no condice con lo dicho: a mi parecer sería algo como:

Set conjunto = new HashSet();

no se si podes decir algo al respecto, te agredeceria desde ya.

fernando

fernando dijo...

uhh, disculpame, no me tomo el simbolo mayor y el simbolo menor, era:

Set<int> conjunto = new HashSet<int>();

y no como puse ahí arriba.

Kelvin dijo...

Bueno, la verdad es que no hay un ejemplo de Generics con tipos primitivos en el articulo. El ejemplo que tu colocas es cierto no es posible hacer:
Set<int> conjunto = new HashSet<int>();

El artículo sólo se hace mencion a que no son posibles, si lees mas detenidamente puedes darte cuenta que el codigo 7 hace mencion a que no es posible crear arreglo de Genericos.

John Ortiz Ordoñez dijo...

Bien por esta información. Le recomiendo una impresora estética (Syntax highlighting) para la presentación del código fuente. Hasta pronto.

Kelvin dijo...

Gracias por tu aporte, para el próximo artículo empezaré a utilizarlo, ya lo he probado y me ha resultado mucho más práctico y estético. Hasta pronto.

Autor dijo...

y como podria crear un metodo generico?

Kelvin dijo...

Para crear un método genérico como lo indica el código 8, debemos colocar entre los corchetes angulares el comodin, es decir, el signo de interrogación. Ejemplo:

public static void procesarComodin(Comodin<?> cmdComodin){
System.out.println("Procesando Comodin");
}

Para mas detalles, detalla el cóodigo 8.

Unknown dijo...

Epale kelvin! te escribo de venezuela! Resulta que he estado buscando informacion acerca de los ipos genericos y he conseguido un monton, pero hasta ahora ninguno me he hablado de que hacer con una interfaz declarada con tipo generico... Te explico mi caso:

Tengo una interfaz declarada como sigue:


public interface List {
...
(un monton de metodos...)
...
}

Como es una interfaz, por supuesto esta vacia. Supongo que debo implementarla de alguna manera. Lo que no llego a entender es si yo debo hacer una implementacion para cada uno de los tipos que yo quisiera que usara la lista, o si deberia hacer una sola implementacion para el tipo Object... la verdad es que estoy bastante crudo con esto y no he conseguido hasta ahora informacion al respecto... bueno, muchas gracias por toda la ayuda que puedas darme! hasta luego!

Kelvin dijo...

Para responder tu pregunta le dedique un nuevo artículo a las interfaces genéricas.

Visita el Articulo Hablemos de interfaces genéricas de java 5.0

Espero pueda ayudarte y resuelva tus dudas. Cualquier duda sigue escribiendo.

heberto dijo...

El comodín no puede utilizarse para crear instancias es sólo para métodos que reciben como parámetro una clase Genérica, podemos ver el ejemplo con procesarComodin.

???????????????????????????

List nums = new ArrayList();

heberto dijo...

List< ?
> nums = new ArrayList();

Unknown dijo...

Me ha parecido genial, muy bien explicado y con ejemplo, perfecto.

Anónimo dijo...

¡Muchas gracias Kelvin!

Muy claro todo.

jefebrondem dijo...

Hola.

Veo que con generics se puede informar la herencia del tipo parametrizado . ¿Es posible hacer lo mismo con interfaces? Es que intento hacer algo de este estilo y no funciona:

class contenedor< T implements Comparable >
{
T data;
public void insert( T new_data )
{
int n = data.compareTo( new_data );
}
}

Gracias

Kelvin dijo...

Dale un vistazo a Interfaces genericas: http://acercadejava.blogspot.com/2010/05/hablemos-de-intefaces-genericas-de-java.html

Anónimo dijo...

buenas amigo
gracias por la gran ayuda!! esta muy bien explicado en mi caso yo recien estoy empezando en el mundo de java y se al intentar hacer un ejemplo simple de clases genericas me compila con todos los datos primitivos menos el Float
quisiera saber el xq`
muchas gracias de antemano aqui te paso el codigo

package preg1;
public class generico {
public T a;
generico(T ge){
this.a=ge;
}
@Override
public String toString() {
return "generico{" + "a=" + a + '}';
}
public T getA() {
return a;
}
public void setA(T a) {
this.a = a;
}
public generico() {

}

public static void main(String[] args) {
System.out.println("primero con el 3,141516 v : ");
generico f = new generico<>();
f.setA(3.141516);
System.out.println("el num es : "+ f.toString());

generico s=new generico<>("La honestidad es una obra Sublime");
System.out.println("El String a digitar es\t : "+s.toString());

generico s = new generico("315.12");
System.out.println(s.toString());



}



}