Érase una
vez, en una galaxia muy, muy lejana, antes de Java 5 y de la
existencia de genéricos, los programadores java que utilizaban
colecciones, se enfrentaban a un temible peligro.
Ellos
podían, por ejemplo, crear una lista de Strings como la siguiente:
List
miLista = new ArrayList( );
miLista.add(“Hola”);
miLista.add(“Galaxia”);
miLista.add(new
Integer(2001));
podían,
incluso, hacer un casting al obtener los elementos de esa lista:
String s
= (String) miLista.get(0);
String st
= (String) miLista.get(1);
String
str = (String) miLista.get(2);
¡Pero nada
de eso evitaba que al ejecutarse el programa se produjera un error!
¿Por qué? ¡Porque el tercer elemento no es un String, sino un
Integer, pero nada impedía añadirlo a la lista! Por suerte, los
genéricos vinieron al rescate de estos desdichados programadores y
nunca más se produjeron errores de este tipo.
Bueno, vale,
puede que la historia no fuese exactamente así, pero nos sirve para
explicar en qué consisten los genéricos y su función. Desde luego
los genéricos no se utilizan sólo con las colecciones, pero es
cierto que es en ellas donde te los vas a encontrar mayoritariamente.
Los
genéricos permiten asignar parámetros a las clases, interfaces,
métodos..., de forma que sólo admitan los tipos de objetos que tu
quieras. No se pueden utilizar con primitivos, pero si con las
clases que se corresponden con ellos. Por eso no puedes hacer un
genérico tipo int, pero sí un Integer. Veamos el ejemplo anterior
utilizando genéricos:
List
<String> miLista = new ArrayList <String>();
//al
poner <String> estamos indicando que la lista sólo acepta
Strings
miLista.add(“Hola”);
//un String, perfecto
miLista.add(“Galaxia”); //otro String, muy bien
miLista.add(new
Integer(2001)); //¡error de compilación, no es un String!
Evidentemente,
es mucho más sencillo solucionar un error de compilación que un
error de ejecución. Además, no hace falta ningún casting para
obtener los elementos de la lista:
String s
= miLista.get(0);
String st
= miLista.get(1);
//el
compilador ya sabe que es un String, no hace falta casting.
Para
conseguir esto, basta poner entre los signos < >, llamados diamante, el tipo al que
queremos que pertenezcan los elementos: String, Integer o lo que sea.
Además, para facilitar más las cosas, a partir de Java 7 la
primera línea de este código se escribiría así:
List
<String> miLista = new ArrayList<>();
porque el
compilador ya infiere de qué tipo son los elementos de la lista.
Pero, ¿cómo
crear nuestra propia clase genérica? Fíjate en este ejemplo:
public
class Cajón<T>{
private
T t;
public
void set (T t){
this.t
= t;
}
public
T get( ){
return
t;
}
}
Ya hemos
construído una clase genérica, y le hemos proporcionado dos
métodos. La T es el tipo de parámetro que le vamos a asignar. Supón
por ejemplo que quieres un Cajón de Calcetines y otro Cajón de
Camisetas, pues sustituyes T por Calcetines o por Camisetas (por
supuesto, si no existen las clases Camisetas ni Calcetines, debes
crearlas) de esta forma:
public
class Cajón<Calcetines> o public class Cajón<Camisetas>
Todos los
métodos de tu clase genérica pueden ser utilizados en cualquiera de
ellas, pero no podrás usar calcetines con Cajón<Camisetas> ni
camisetas con Cajón<Calcetines>.
Por
convención, los nombres de los tipos de parámetros se escriben como
una letra mayúscula, y los más habituales son :
- T, de tipo
- E, de elemento, muy usado con la Java Collection Framework
- N, de número (class Number)
- K, de key
- V, de value, estas dos últimas utilizadas sobre todo en mapas (Map)
¿Y cómo
sería un método en el que estemos utilizando genéricos?
void
añadirCalcetines (Cajón<Calcetines> cal){
cal.add(new
Calcetines( ));
}
Un método
muy simplón, pero sirve para que veas la sintaxis. Y hablando de
sintaxis, es muy habitual con los genéricos ver esto: <?>
acompañado a veces de “extends” o “super”. Ahora mismo te
explico lo que significa. Supongamos que tienes una lista de números
enteros:
List<Integer>
miLista = new List<>( );
y piensas,
“bueno, Integer es una subclase de Number, así que puedo hacer
esto” y escribes:
List<Number>
miLista = new List<Integer>( );
¡No
compila! ¿Por qué? ¡Porque List<Integer> no es un subtipo de
List<Number>! En List<Number> sólo pueden entrar objetos
que pertenezcan a la clase Number, no objetos de la clase Integer, ni
de la clase Double, ni Short, ni Long. Sólo de la clase Number, por
eso, aunque a la lista puedes añadirle números enteros, o decimales
o los que quieras que pertenezcan a Number, no puedes decir que es
una nueva lista de números enteros. Ahora bien, si escribimos esto:
List<?
extends Number>
estamos
indicando que esta lista va a admitir objetos de la clase Number y
de cualquier otra que la extienda (“?”
significa cualquiera). Así pues, modificamos
List<?extends
Number> miLista = new List<Integer>( );
y
ahora sí que compila. Y también compilará si en lugar de
List<Integer> utilizamos cualquier otra subclase de Number,
como List<Double>.
Pero,
¿qué ocurre si lo que queremos es lo contrario, es decir, que la
lista admita objetos de la clase Integer y su superclase, pero no
ninguna otra subclase de Number?
En
ese caso, haríamos lo siguiente:
List<? super
Integer>
con
lo que la lista admitirá objetos de la clase Integer y de cualquier
otra clase superior a ella, como Number.
¿Y
qué ocurre si hacemos esto?:
List<?>
Lo
que estamos indicando ahora es que la lista admitiría cualquier tipo
de objetos. No confundir con “objetos de la clase Object”. Es
decir, esto:
List<Object>
sólo
admite objetos que pertenezcan a la clase Object, y no podemos hacer
lo siguiente:
List<Object>
miLista = new List<Calcetines>( ); //no compila
Por
último, quiero añadir que es cierto que los genéricos en Java no
son perfectos (como los programadores en C siempre señalan amablemente)
pero eso es así porque desde un principio se busco que fuese
compatible con todo el código escrito con anterioridad a Java 5, que
no los utilizaba. No era cuestión de que todo ese código dejase de
funcionar, ¿no?
Si has tenido dificultades en seguir esta entrada porque no sabes lo que son las colecciones, puedes ver esta entrada anterior. Si el problema es con el polimorfismo y la herencia, consulta esta otra. Para ampliar conocimientos, consulta los tutoriales de Oracle.
Si tienes dudas, pregúntame. Y si te ha gustado, compártelo.;-)
En respuesta a la consulta de Anet (ver Comentarios)
Hola, Anet:
¡Es un placer ver una chica por aquí!
Respecto a tu pregunta, no tengo muy claro qué es exactamente lo que quieres hacer, así que no sé si la respuesta será la que buscas.
Como ya hemos visto, los genéricos siguen las reglas de herencia de Java a nivel de tipo, pero no a nivel de parámetro.
Por ejemplo, podemos hacer
Set<String> mySet = new HashSet<String>();
porque HashSet es un subtipo de Set, pero no
Set<Object> mySet = new HashSet<String>();
porque sólo puedes usar Object como parámetro, no String, y por eso utilizamos los wildcards y extends, como muestro arriba.
Con las interfaces, sería lo mismo, solo que en lugar de extends, usaríamos implements.
A continuación te dejo un ejemplo de un código, donde creo una clase que va a ser un subtipo de ArrayList. Fíjate que por eso no necesitamos que mi clase implemente la interfaz List, porque eso ya lo hace ArrayList.
Además, vamos a hacer que implemente una interfaz que yo he creado, de esa manera podrás ver cómo se hace.
import java.util.*;
public class TestGen{
public static void main(String args[]){
MyGen<String> miLista = new MyGen<String>();
miLista.add(0,"A");
miLista.add(1,"B");
miLista.add(2,"C");
for (String item: miLista){
System.out.println(item);
}
ArrayList<String> miOtraLista = new MyGen<String>();
miOtraLista.addAll(miLista);
for (String item: miOtraLista){
System.out.println(item);
}
if (miOtraLista.containsAll(miLista)){
System.out.println("Tienen los mismos elementos");
}
String miEjemplo = miLista.ejemplo().toString();
System.out.println(miEjemplo);
}
}
//La clase MyGen extiende ArrayList (que a su vez implementa List) y además implementa la interfaz //Ejemplo
class MyGen<String> extends ArrayList<String> implements Ejemplo<Double>{
//aquí sustituímos T por Double, pero podríamos poner cualquier otro tipo
public MyGen(){
super();
}
//nuestra versión del método ejemplo
@Override
public Double ejemplo(){
return 15.25;
}
}
//creamos la interfaz usando T, de forma que cuando la implementemos podamos usar el tipo que //queramos.
interface Ejemplo<T>{
T ejemplo();
}
De esta manera, la clase MyGen puede utilizar los métodos de ArrayList, que a su vez tiene los de las interfaces que implementa, y además va a poder usar el método de la interfaz Ejemplo.
Espero que ésto te ayude a aclarar los conceptos.
¡Un saludo!
En respuesta a la consulta de Anet (ver Comentarios)
Hola, Anet:
¡Es un placer ver una chica por aquí!
Respecto a tu pregunta, no tengo muy claro qué es exactamente lo que quieres hacer, así que no sé si la respuesta será la que buscas.
Como ya hemos visto, los genéricos siguen las reglas de herencia de Java a nivel de tipo, pero no a nivel de parámetro.
Por ejemplo, podemos hacer
Set<String> mySet = new HashSet<String>();
porque HashSet es un subtipo de Set, pero no
Set<Object> mySet = new HashSet<String>();
porque sólo puedes usar Object como parámetro, no String, y por eso utilizamos los wildcards y extends, como muestro arriba.
Con las interfaces, sería lo mismo, solo que en lugar de extends, usaríamos implements.
A continuación te dejo un ejemplo de un código, donde creo una clase que va a ser un subtipo de ArrayList. Fíjate que por eso no necesitamos que mi clase implemente la interfaz List, porque eso ya lo hace ArrayList.
Además, vamos a hacer que implemente una interfaz que yo he creado, de esa manera podrás ver cómo se hace.
import java.util.*;
public class TestGen{
public static void main(String args[]){
MyGen<String> miLista = new MyGen<String>();
miLista.add(0,"A");
miLista.add(1,"B");
miLista.add(2,"C");
for (String item: miLista){
System.out.println(item);
}
ArrayList<String> miOtraLista = new MyGen<String>();
miOtraLista.addAll(miLista);
for (String item: miOtraLista){
System.out.println(item);
}
if (miOtraLista.containsAll(miLista)){
System.out.println("Tienen los mismos elementos");
}
String miEjemplo = miLista.ejemplo().toString();
System.out.println(miEjemplo);
}
}
//La clase MyGen extiende ArrayList (que a su vez implementa List) y además implementa la interfaz //Ejemplo
class MyGen<String> extends ArrayList<String> implements Ejemplo<Double>{
//aquí sustituímos T por Double, pero podríamos poner cualquier otro tipo
public MyGen(){
super();
}
//nuestra versión del método ejemplo
@Override
public Double ejemplo(){
return 15.25;
}
}
//creamos la interfaz usando T, de forma que cuando la implementemos podamos usar el tipo que //queramos.
interface Ejemplo<T>{
T ejemplo();
}
De esta manera, la clase MyGen puede utilizar los métodos de ArrayList, que a su vez tiene los de las interfaces que implementa, y además va a poder usar el método de la interfaz Ejemplo.
Espero que ésto te ayude a aclarar los conceptos.
¡Un saludo!
muy bueno
ResponderEliminarMuchas gracias, Anónimo. :-)
Eliminarbuenisimo... gracias :), sali de la duda en cuanto al famoso extends y su famoso ?
ResponderEliminarGracias, a ti, por leerlo! :-)
Eliminarhola, una pregunta podrías proporcionarme un código con herencia, interfaz y tipos genéricos es que no se como mesclarlos, gracias y saludos.
ResponderEliminarHola, Anet:
EliminarPor algún motivo, Blogger no me deja responder a tu pregunta como yo quisiera, así que he añadido mi respuesta al final de la entrada. ¡Espero que te sirva!
Mil gracias !
ResponderEliminarGracias y felicidades, tenia problemas con este concepto pero con tu explicacion en forma de preguntas fue MUY BUENA!!!!
ResponderEliminarMuchas gracias, Jesús. Me alegro de que te haya servido.:-)
EliminarExcelente explicación.
ResponderEliminarOMG!!!! en ningun otro libro,y desde hace mucho tiempo que no entendia esto, esta tan bien explicado como aqui!!!!! Aplausos!!! lovin it :D!!
ResponderEliminarAndrea.