====== Genéricos ======
===== Genéricos =====
El uso más conocido de los genéricos lo vimos en el tema anterior, con las colecciones, ya que éstas están definidas como clases genéricas,
lo que permite que podamos definir estructuras de la siguiente forma:
List listaCadenas = new ArrayList<>();
Lo que permite que, si cometemos algún error como este, podamos ser avisados en tiempo de compilación:
Integer i = listaCadenas.get(0); // Error de compilación
Sin la existencia de los genéricos, la línea anterior sería como esta:
Integer i = listaCadenas.get(0); // Nunca daría error de compilación
Y nunca produciría ningún error de compilación en caso de que estuvieramos cometiendo algún error. A este respecto conviene saber que los
genéricos sólo existen a nivel de compilación, para evitar este tipo de fallos. En el código generado se añaden todos los ''casting''
necesarios para que el código pueda ejecutarse sin problemas.
==== Definición de una clase genérica ====
El siguiente fragmento de código se correspondería con la definición de una clase genérica:
public class Grupo {
private List participantes;
private int limite;
public Grupo(int limite) {
this.limite = limite;
participantes = new ArrayList<>();
}
public void anadirParticipante(E participante) {
if (participantes.size() == limite)
return;
participantes.add(participante);
}
public E getParticipante(int posicion) {
return participantes.get(posicion);
}
public int getNumeroParticipantes() {
return participantes.size();
}
public int getEspacioLibre() {
return (limite - participantes.size());
}
}
De esa forma, una definición genérica como la anterior, puede emplearse con distintos tipos de datos:
Grupo grupoAlumnos = new Grupo<>(20);
. . .
Grupo grupoProfesores = new Grupo<>(10);
. . .
Como has podido ver en la definición, al tipo genérico va entre los caracteres < y > definido con una sola letra mayúscula, y una vez
definida dicha letra en la definición de la clase, se emplea siempre que se quiera hacer referencia al tipo para el que se define dicha
clase genérica. A este respecto se suelen utilizar diferentes letras para diferentes situaciones:
* ''E'' para definir un elemento de una colección
* ''K'' para definir una clave en un mapa
* ''V'' para definir un valor en un mapa
* ''T'', ''U'' para definir tipos de datos (clases)
==== Métodos genéricos ====
Así como se pueden definir clases genéricas, también podemos definir métodos genéricos dentro de clases que no lo sean:
public class Utils {
public T getUltimoElemento(ArrayList lista) {
return lista.get(lista.size() - 1);
}
}
De esa manera, se podrá invocar al método ''getUltimoElemento'' con cualquier ''ArrayList'', independientemente del tipo que sea.
==== El comodín ? ====
También se puede utilizar el caracter comodín ''?'' para indicar que se acepta cualquier tipo, definiendo, por ejemplo, como parámetro un
\verb ArrayList de la siguiente forma:
. . .
public void metodo(ArrayList> elementos) {
. . .
. . .
}
. . .
Para este caso también podemos aprovecharnos de las características de la herencia para la definición de métodos genéricos, pudiendo definir el uso de la
clase genérica como clase derivada o como superclase de una, haciendo uso de este comodín.
Para aceptar cualquier clase que extienda de una determinada clase:
. . .
public void Zoo {
. . .
public void anadirAnimales(ArrayList extends Animal> animales) {
. . .
. . .
}
. . .
}
. . .
Y para aceptar cualquier clase que esté por encima de una determinada clase:
. . .
public void Zoo {
. . .
public void anadirAnimales(ArrayList super Animal> animales) {
. . .
. . .
}
. . .
}
. . .
De esa manera, podremos invocar al método \verb anadir de múltiples formas, siempre y cuando pasemos como parámetro \verb ArrayList un tipo
que corresponda con la expresión del comodín que acompaña a la declaración de ese \verb ArrayList . Para entender esta parte hay que tener en cuenta el siguiente caso, y es que dadas las
siguientes clases:
public class Animal {
. . .
}
public class Leon extends Animal {
. . .
}
public class Zoo {
. . .
public void anadirAnimales(ArrayList animales) {
. . .
. . .
}
. . .
}
Las siguiente invocaciones producirían un error de compilación por incompatibilidad de tipos:
. . .
Zoo zoo = new Zoo(. . .);
. . .
ArrayList leones = new ArrayList<>();
zoo.anadirAnimales(leones); // Error de compilación
. . .
Es por eso que tenemos que usar lo que se conoce como los comodínes.
Para el caso de las clases genéricas y métodos genéricos también podemos jugar con las características de herencia, utilizando las mismas
expresiones que podemos usar con el comodín, pero con el caracter que empleemos para referirnos a la clase genérica. A diferencia del uso
del comodín ''?'', podremos hacer referencia a la clase genérica a lo largo de la implementación del método o clase.
public class Grupo {
private List participantes;
private int limite;
public Grupo(int limite) {
this.limite = limite;
participantes = new ArrayList<>();
}
public void anadirParticipante(E participante) {
if (participantes.size() == limite)
return;
participantes.add(participante);
}
public E getParticipante(int posicion) {
return participantes.get(posicion);
}
public int getNumeroParticipantes() {
return participantes.size();
}
public int getEspacioLibre() {
return (limite - participantes.size());
}
}
. . .
public T getAnimal(ArrayList animales) {
. . .
// Podemos hacer rerencia a T como clase genérica
. . .
return T;
}
. . .
----
(c) 2019-{{date>%Y}} Santiago Faci