Utilizando Genéricos en Java

COMPARTIR EN REDES SOCIALES

En esta entrada vamos a profundizar sobre los tipos genéricos en Java. Los tipos genéricos en Java nos permiten crear métodos y clases cuyos tipos de parámetros pueden ser variables, y por lo tanto, nos permiten implementar menos código para realizar nuestras funcionalidades. Los genéricos en Java son el mismo concepto que los «templates» en C++.

Los genéricos en Java se pueden aplicar sobre funciones y sobre clases, y por lo tanto, también en las funciones de nuestras clases. Sin más dilaciones vamos al lio con los genéricos en funciones.

Utilizando Genéricos en Funciones

Supongamos que queremos crear una función que nos permita convertir un Array estático a un ArrayList, y queremos que nos sirva para tipos «Integer», «Float», y «Double». Sin utilizar genéricos tendríamos que realizar 3 implementaciones. Vamos primero a ver nuestros Arrays estáticos:

Integer [] numerosEnteros = new Integer [] {1,2,3,4,5};
Float [] numerosFlotantesSimples = new Float[] {1.0f, 2.0f, 3.0f, 4.0f, 5.0f};
Double [] numerosFlotantes = new Double[] {1.0, 2.0, 3.0, 4.0, 5.0};

Veamos las diferentes implementaciones para convertir cada Array estático a ArrayList del mismo tipo:

//Para Integers
public static ArrayList<Integer> convertToArrayList(Integer [] array){
	ArrayList<Integer> miLista = new ArrayList<Integer>();
		
	for (Integer numero: array) {
		miLista.add(numero);
	}
		
	return miLista;
}
	
//Para Float's
public static ArrayList<Float> convertToArrayList(Float [] array){
	ArrayList<Float> miLista = new ArrayList<Float>();
		
	for (Float numero: array) {
		miLista.add(numero);
	}
		
	return miLista;
}
	
//Para Doubles
public static ArrayList<Double> convertToArrayList(Double [] array){
	ArrayList<Double> miLista = new ArrayList<Double>();
		
	for (Double numero: array) {
		miLista.add(numero);
	}
		
	return miLista;
}

Hemos tenido que implementar una función para cada tipo de datos.

¿ Podríamos crear una única función utilizando Genéricos que nos sirva para los 3 Arrays ?

Sí, claro que podemos, utilizando genéricos. Veamos primero la implementación con «genéricos» y luego explicaremos como funcionan:

public static <V> ArrayList<V> convertToArrayList(V [] array){
	ArrayList<V> miLista = new ArrayList<>();
		
	for (V numero: array){
		miLista.add(numero);
	}
		
	return miLista;
}

Vayamos por orden de los resaltados:

  1. «<V>»: De esta forma estamos parametrizando que V puede ser un Objeto de cualquier tipo.
  2. «ArrayList<V>»: será el tipo de datos que devolveremos.
  3. «V [] array»: será el Array estático que nos llegará como parámetro de entrada.
  4. «V numero»: para iterar sobre nuestro Array estático nuestros elementos serán de tipo «V».

Ahora imaginad que en tiempo de compilación Java sustituye nuestra V por el tipo de datos que detecta en la llamada a la función, de tal forma que sí llamamos a la función con un Array de «Float», V se sustituirá por «Float», si lo hacemos con «Integer» igual, y así sucesivamente.

Hemos conseguido con una única función crear la 3 implementaciones.

¿ Y cómo llamamos a la función para los 3 Arrays ?

//LLamada para enteros
ArrayList<Integer> listaEnteros = convertToArrayList(numerosEnteros);
//LLamada para flotantes
ArrayList<Float> listaFloat = convertToArrayList(numerosFlotantesSimples);
//LLamada para Doubles
ArrayList<Double> listaDouble = convertToArrayList(numerosFlotantes);
		
//Comprobación de que ha funcionado
System.out.println(listaEnteros.get(0).getClass().getName());
System.out.println(listaFloat.get(0).getClass().getName());
System.out.println(listaDouble.get(0).getClass().getName());

La salida de nuestro programa:

java.lang.Integer
java.lang.Float
java.lang.Double

Cómo podéis apreciar ha funcionado perfectamente, una única función parametrizable por tipo, ¿ no os parece una pasada ?

Utilizando Genéricos en Clases

En las clases también podemos utilizar genéricos y parametrizar los tipos de datos. Para entender como funciona vamos a partir de la siguiente clase «Persona»:

public class Persona {
	private String nombre;
	private String apellidos;
	private Integer edad;
	
	public Persona(String nombre, String apellidos, Integer edad) {
		super();
		this.nombre = nombre;
		this.apellidos = apellidos;
		this.edad = edad;
	}

	public String getNombre() {
		return nombre;
	}

	public void setNombre(String nombre) {
		this.nombre = nombre;
	}

	public String getApellidos() {
		return apellidos;
	}

	public void setApellidos(String apellidos) {
		this.apellidos = apellidos;
	}

	public Integer getEdad() {
		return edad;
	}

	public void setEdad(Integer edad) {
		this.edad = edad;
	}
}

Ahora supongamos que queremos que la misma clase pueda almacenar la edad tanto en «Integer» como en «Double». Ya estamos otra vez en las mismas, nuestra implementación de «Persona» solo contempla «Integer». Vamos a transformar nuestra clase para que el atributo edad sea «genérico»:

public class Persona <V>{
	private String nombre;
	private String apellidos;
	private V edad;
	
	public Persona(String nombre, String apellidos, V edad) {
		super();
		this.nombre = nombre;
		this.apellidos = apellidos;
		this.edad = edad;
	}

	public String getNombre() {
		return nombre;
	}

	public void setNombre(String nombre) {
		this.nombre = nombre;
	}

	public String getApellidos() {
		return apellidos;
	}

	public void setApellidos(String apellidos) {
		this.apellidos = apellidos;
	}

	public V getEdad() {
		return edad;
	}

	public void setEdad(V edad) {
		this.edad = edad;
	}
}

Para ver la explicación vamos por orden:

  1. public class Persona <V>: Aquí estamos indicando que el tipo de datos «V» es un genérico.
  2. private V edad: Nuestro atributo «edad» será de tipo «V».
  3. Persona(String nombre, String apellidos, V edad): En el constructor nuestro parámetro de entrada «edad» será de tipo «V».
  4. public V getEdad(): Nuestro «getter» de «edad» devolverá la edad con el tipo de datos «V».
  5. public void setEdad(V edad): Nuestro «setter» recibirá como parámetro la edad con el tipo de datos «V»

Ya hemos implementado nuestra clase para que admita la edad con «Integer» y «Double» y en general con cualquier tipo de datos, ya que «V» es un Object, por lo tanto, podría ser cualquier clase.

¿ Cómo se instancian las clases Genéricas ?

Simplemente tenemos que parametrizar con «<>» su definición e instanciación:

//Instanciamos una clase Persona con la edad en Integer
Persona<Integer> persona1 = new Persona<Integer>("Somos", "Hackers de la Programacion", 32);
//Instanciamos una clase Persona con la edad en Double
Persona<Double> persona2 = new Persona<Double>("Somos", "Hackers de la Programacion", 32.3);

Vamos a comprobar su funcionamiento y ver si realmente funciona:

//Comprobamos su funcionamiento
System.out.println("La persona 1 tiene: " + persona1.getEdad() + " años");
System.out.println("El atributo edad de persona 1 es: " + persona1.getEdad().getClass().getName());
System.out.println("La persona 2 tiene: " + persona2.getEdad() + " años");
System.out.println("El atributo edad de persona 2 es: " + persona2.getEdad().getClass().getName());

La salida de nuestro programa:

La persona 1 tiene: 32 años
El atributo edad de persona 1 es: java.lang.Integer
La persona 2 tiene: 32.3 años
El atributo edad de persona 2 es: java.lang.Double

¿ Se puede utilizar más de un tipo Genérico en las Funciones y Clases ?

Sí, basta con declarar la lista separada por «,». Veamos un ejemplo de nuestra función:

public static <V, V2> ArrayList<V> convertToArrayList(V [] array, V2 otroTipoDeDato){
	ArrayList<V> miLista = new ArrayList<>();
		
	for (V numero: array){
		miLista.add(numero);
	}
		
	System.out.println(otroTipoDeDato);
		
	return miLista;
}

¿ Como sería ahora la llamada a la función ?

//LLamada para enteros
ArrayList<Integer> listaEnteros = convertToArrayList(numerosEnteros, "Otro tipo de dato genérico de parámetro");
//LLamada para flotantes
ArrayList<Float> listaFloat = convertToArrayList(numerosFlotantesSimples, "Otro tipo de dato genérico de parámetro");
//LLamada para Doubles
ArrayList<Double> listaDouble = convertToArrayList(numerosFlotantes, "Otro tipo de dato genérico de parámetro");

La salida de nuestro programa

Otro tipo de dato genérico de parámetro
Otro tipo de dato genérico de parámetro
Otro tipo de dato genérico de parámetro
java.lang.Integer
java.lang.Float
java.lang.Double

¿ Y en las clases ?

Se sigue la misma metodología:

public class Persona <V, V2, V3>{
	private V2 nombre;
	private V3 apellidos;
	private V edad;
	
	public Persona(V2 nombre, V3 apellidos, V edad) {
		this.nombre = nombre;
		this.apellidos = apellidos;
		this.edad = edad;
	}

	public V2 getNombre() {
		return nombre;
	}

	public void setNombre(V2 nombre) {
		this.nombre = nombre;
	}

	public V3 getApellidos() {
		return apellidos;
	}

	public void setApellidos(V3 apellidos) {
		this.apellidos = apellidos;
	}

	public V getEdad() {
		return edad;
	}

	public void setEdad(V edad) {
		this.edad = edad;
	}
}

Y podemos crear nuestras clases con los parámetros totalmente parametrizables:

//Instanciamos la primera persona
Persona<Integer, String, StringBuilder> persona1 = new Persona<Integer, String, StringBuilder>("Somos", new StringBuilder("Hackers de la Programación"), 32);
//Instanciamos la segunda persona
Persona<Double, String, StringBuilder> persona2 = new Persona<Double, String, StringBuilder>("Somos", new StringBuilder("Hackers de la Programación"), 32.3);
		
//Comprobamos su funcionamiento
System.out.println(persona1.getNombre() + " " + persona1.getApellidos().toString() + " tiene " + persona1.getEdad() + " años");
System.out.println(persona2.getNombre() + " " + persona2.getApellidos().toString() + " tiene " + persona2.getEdad() + " años");

La salida de nuestro programa

Somos Hackers de la Programación tiene 32 años
Somos Hackers de la Programación tiene 32.3 años

Y esto es todo con los Genéricos en Java.

Si os ha gustado la entrada dejad un comentario en la caja de comentarios, y si no habéis entendido algo también, así lo podremos debatir!

Os dejo un enlace con la documentación oficial de Oracle.

Podéis también ver nuestra entrada implementación de un ArrayList Genérico en Java


COMPARTIR EN REDES SOCIALES

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *