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<String> 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.
El siguiente fragmento de código se correspondería con la definición de una clase genérica:
public class Grupo<E> { private List<E> 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<Alumno> grupoAlumnos = new Grupo<>(20); . . . Grupo<Profesor> 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ónK
para definir una clave en un mapaV
para definir un valor en un mapaT
, U
para definir tipos de datos (clases)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> T getUltimoElemento(ArrayList<T> 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.
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<Animal> animales) { . . . . . . } . . . }
Las siguiente invocaciones producirían un error de compilación por incompatibilidad de tipos:
. . . Zoo zoo = new Zoo(. . .); . . . ArrayList<Leon> 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<E extends Persona> { private List<E> 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 extends Animal> T getAnimal(ArrayList<T extends Animal> animales) { . . . // Podemos hacer rerencia a T como clase genérica . . . return T; } . . .
© 2019-2024 Santiago Faci