Con la classe ArrayList hem pogut comprovar com podíem fer una llista de qualsevol objecte, ja que aquesta classe utilitza aquest paradigma de Java, que es diu programació genèrica.
La programació genèrica consisteix a fer un codi que puguis reutilitzar per a objectes de diversos tipus, i aquí està la clau, la reutilització del codi. D'aquesta manera evites crear diverses classes concretes per a cadascun dels objectes. De tal manera, amb ArrayList podem guardar qualsevol tipus d'objecte.
En la classe ArrayList modifiquem el paràmetre de tipus, per exemple <String>.
Se li denomina genèrica perquè estàs fent una programació d'una classe que gestiona objectes en general.
Podríem fer el mateix per herència, amb una única classe que sigui capaç de gestionar objectes de diferent tipus. Ja sabem que a Java tenim la classe Object de la qual hereden les altres; utilitzant aquestes podríem arribar a fer servir classes de qualsevol tipus, però porta diversos inconvenients:
- Ús continu del casting.
- Complicació del codi.
- No tens la possibilitat de comprovar errors d'execució.
En canvi, la programació genèrica té:
- Més senzillesa del codi.
- Reutilització del codi en nombrosos escenaris.
- Comprovació d'errors en temps de compilació.
La programació genèrica no existia fins a la versió 5.0 de Java. Si haguéssim de fer això sense classes genèriques tindríem alguns problemes...
Podem veure un exemple de com seria una classe ArrayList abans de la versió 5.0 i els seus desavantatges.
package ArrayList;
public class ArrayList {
private Object[] datosElemento;
private int i=0;
public ArrayList(int z){
datosElemento = new Object[z];
}
ArrayList() {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
public Object get(int i){
return datosElemento[i];
}
public void add(Object o){
datosElemento[i]=o;
i++;
}
}
Un exemple de l'ús d'aquesta seria el següent.
package ArrayList;
import java.io.*;
public class ArrayList_Ejemplo {
public static void main(String[] args){
ArrayList archivos = new ArrayList(6);
archivos.add("Pepe");
archivos.add("Maria");
archivos.add("Juanito");
archivos.add("Jesus");
archivos.add("Fran");
archivos.add(new File("gestion.ves"));
String nombre = (String) archivos.get(2); //Inconveniente del casting
System.out.println(nombre);
//Las siguientes lineas no marcarán errores pero petará en tiempo de ejecución, esto no pasa con la programación generica.
//String problema = (String) archivos.get(5); //Inconveniente del casting
//System.out.println("Problema : " + problema);
ArrayList files = new ArrayList(5);
files.add(new File("gestion_Pedidos.bla"));
File archivo = (File) files.get(0);
System.out.println(archivo);
}
}
Crear una classe genèrica pròpia
Per veure-ho, serà millor veure un exemple.
Si tenim la classe genèrica creada per nosaltres, anomenada Pareja, on T és un objecte qualsevol que serà definit en la instanciació de la classe, igual com passava amb els ArrayList. Com podem observar, en comptes de posar String o un tipus, sempre posarem T, d'aquesta manera quan puguem entre <> el nom del tipus d'objecte que haurà d'emmagatzemar, tot passarà a ser d'aquell tipus i d'aquesta manera ho podem adaptar per a qualsevol situació.
Per exemple, Pareja quedaria així.
package ClaseGenerica;
public class Pareja<T> {
private T primero;
public Pareja(){
primero=null;
}
public void setPrimero(T nuevoValor){
primero = nuevoValor;
}
public T getPrimero(){
return primero;
}
}
I podem observar uns exemples:
package ClaseGenerica;
public class Pareja_Ejemplo {
public static void main(String[] args){
Pareja<String> ejemploString = new Pareja<>();
ejemploString.setPrimero("prueba");
System.out.println(ejemploString.getPrimero());
Pareja<Persona> ejemploPersona = new Pareja<>();
Persona persEjemplo = new Persona("fmesasc");
ejemploPersona.setPrimero(persEjemplo);
System.out.println(ejemploPersona.getPrimero());
//La clase generica se adapta al elemento.
}
}
class Persona{
public Persona(String nombre){
this.nombre = nombre;
}
@Override
public String toString(){
return nombre;
}
private String nombre;
}
D'aquesta manera podem observar, amb qualsevol IDE com NetBeans, que una vegada instanciada Pareja<String>, tots els valors que eren T passaran a ser <String> fins i tot en les suggerències.
Mètodes genèrics
Un mètode genèric seria el mateix que una classe genèrica però en mètode. Poden estar dins de classes ordinàries o classes genèriques. Podem veure-ho amb el següent exemple.
public class MetodosGenericos {
public static void main(String[] args){
String nombres[]={"Pepe","Mónica","Francisco"};
System.out.println(MisMatrices.getElementos(nombres));
//Si queremos el elemento menor:
System.out.println(MisMatrices.getMenor(nombres));
}
}
class MisMatrices{
public static <T> String getElementos(T[] a){
return "El array tiene: " + a.length + " elementos";
}
//Todos los objetos tendran que tener implementado Comparable, sino dará error.
public static <T extends Comparable> T getMenor(T[] aux){
if(aux==null || aux.length==0){
return null;
}
T elementoMenor = aux[0];
for(int i=1;i<aux.length;i++){
if(elementoMenor.compareTo(aux[i])>0){
elementoMenor=aux[i];
}
}
return elementoMenor;
}
}
Herència i tipus comodí
El tema de l'herència no funciona igual en les classes genèriques que en les ordinàries. Per exemple, una classe Jefe pot heretar d'empleado i usar la substitució; si podem dir que un cap sempre és un empleat, jefe hereda d'empleado, però un empleat no té per què ser un cap. Doncs aquest principi de substitució no funciona en les classes genèriques. Si tenim una classe genèrica d'empleat i de cap no podem relacionar-los en les classes genèriques. Aleshores hauríem de fer un mètode que rebi un argument amb un genèric de tipus empleat, així doncs aquí dins podem utilitzar-ho mitjançant l'extends del pare de l'herència, una cosa així com Pareja<? extends Empleado> p, seria el paràmetre que se li passa, d'aquesta manera pot ser empleat o cap directament.