Uno de los problemas de usar colecciones en versiones anteriores a java 5 era que el compilador nos permitía insertar un tipo incorrecto en una colección. Por ejemplo teniendo un ArrayList, podríamos insertar una amplia variedad de objetos sin ningún chequeo en tyiempo de compilación. Luego en tiempo de ejecución tendríamos un error de casteo al sacar el objeto con el cast equivocado.
Veamos un ejemplo de como se soluciona esto utilizando genericos:
import java.util.*;
class ManzanaRoja extends Manzana {}
class ManzanaVerde extends Manzana {}
public class Ejemplo {
public static void main(String[] args) {
//define el tipo del ArrayList
ArrayList<Manzana> manzanas = new ArrayList<Manzana>();
manzanas.add(new ManzanaRoja());
manzanas.add(new ManzanaVerde());
for(Manzana c : manzanas)
System.out.println(c);
}
}
Una de las principales motivaciones de usar generics es poder especificar que tipo de objeto contiene un contenedor y tener esa especificación soportada por el compilador
Veamos una clase utilizando genericos
public class Holder<T> {
private T a;
public Holder(T a) { this.a = a; }
public void set(T a) { this.a = a; }
public T get() { return a; }
public static void main(String[] args) {
Holder<Automobil> h3 = new Holder<Automobil>(new Automobil());
Automobil a = h3.get(); // No cast needed
// h3.set(”No es un Automobil”); // Error
// h3.set(1); // Error
}
}
Un concepto interesante es el de tupla, una agrupación de objetos como si fuera uno solo, que permite que un método devuelva mas de un objeto
public class TwoTuple<A,B> {
public final A first;
public final B second;
public TwoTuple(A a, B b) { first = a; second = b; }
public String toString() {
return “(” + first + “, ” + second + “)”;
}
}
public class ThreeTuple<A,B,C> extends TwoTuple<A,B> {
public final C third;
public ThreeTuple(A a, B b, C c) {
super(a, b);
third = c;
}
public String toString() {
return “(” + first + “, ” + second + “, ” + third +”)”;
}
}
class Vehiculo {}
public class TupleTest {
static TwoTuple<String,Integer> f() {
return new TwoTuple<String,Integer>(”hi”, 47);
}
static ThreeTuple<Vehiculo,String,Integer> g() {
return new ThreeTuple<Vehiculo, String, Integer>(new Anfibio(), “hi”, 47);
}
public static void main(String[] args) {
TwoTuple<String,Integer> ttsi = f();
System.out.println(ttsi); //(hi, 47)
System.out.println(g()); //(Vehiculo@1f6a7b9, hi, 47)
}
}
Los genéricos tambien funcionan con interfaces:
public interface Generator<T> { T next(); }
Método genéricos:
public class GenericMethods {
public <T> void f(T x) {
System.out.println(x.getClass().getName());
}
public static void main(String[] args) {
GenericMethods gm = new GenericMethods();
gm.f(”");
gm.f(1);
gm.f(1.0);
gm.f(1.0F);
gm.f(‘c’);
gm.f(gm);
}
}
/* Salida:
java.lang.String
java.lang.Integer
java.lang.Double
java.lang.Float
java.lang.Character
GenericMethods
Un tema importante de genéricos es que no permite primitivas como tipo de parámetro. No se puede crear por ejemplo un ArrayList<int>. La solución es utilizar clases wraper en conjunción con el autoboxing de java 5.
Si creamos un ArrayList<Integer> y usamos primitivas, el autoboxing hace la conversión automáticamente.
Otro detalle es que los generics no soportan sobrecarga de métodos. Este ejemplo no compila:
import java.util.*;
public class UseList<W,T> {
void f(List<T> v) {}
void f(List<W> v) {}