Skip to content

10.3 Genéricos avanzados

Limitación de los tipos posibles en un genérico

A veces no interesa que las clases acepten a cualquier tipo de objeto, sino a objetos de un determinado tipo y sus descendientes. Eso es posible indicarlo mediante la palabra extends dentro de la declaración del genérico. Ejemplo:

public class HandlerVehicle <V extends Vehicle>{
    protected V vehicle;
    public HandlerVehicle(V vehicle){
        this.vehicle = vehicle;
    }

    public void start(){
        vehicle.accelerate(100);
    }
}

En el ejemplo, a la clase se le puede indicar cualquier clase descendiente de Vehicle, pero ninguna otra. Por ejemplo:

HandlerVehicle<Car> v1 = new HandlerVehicle<Car>();
HandlerVehicle<Autocar> v2 = new HandlerVehicle<Autocar>

Sin embargo, no se podría declarar:

HandlerVehicle<String> v3 = HandlerVehicle<String>(e);

No es posible porque la clase String no es heredera de ningún vehículo. La razón de utilizar esta cláusula tan restrictiva está en el hecho de poder realizar acciones que sabemos que sólo son posibles en cierto tipo de objetos. La clase HandlerVehicle necesita arrancar el vehículo, por ello tenemos que asegurar que el genérico no puede ser ninguna clase incompatible.

Por otro lado extends no tiene por qué referirse a clases, también puede referirse a interfaces. Ejemplo:

public class Clase1 <V extends Comparable>{

Significa que la Clase1 usa un genérico al que se le puede asignar cualquier clase que implemente la interfaz Comparable.

Tipos comodín

Aunque parece que todo está resuelto de esta forma. Hay problemas cuando mezclamos objetos de la misma clase pero distinta traducción de genérico. Es el caso de este ejemplo:

public class RandomElement<T> {
    private final T element;
    private final int index;

    public RandomElement(T[] array){
        index = new Random().nextInt(array.length - 1);
        element = array[index];
    }

    public T getElement(){
        return element;
    }

    public static void main(String[] args) {
        String[] a = {"uno", "dos", "tres", "cuatro"};
        RandomElement<String> e = new RandomElement<String>(a);

        System.out.println(e.getElement());
    }

    public int getIndex(){
        return index;
    }

    public boolean sameIndex(RandomElement<T> random){
        return random.index == index;
    }
}

En este caso la clase RandomElement se le ha añadido una propiedad que almacena el índice aleatorio que obtiene el constructor. Eso permite que construyamos un método llamado sameIndex que recibe un objeto de clase RandomElement y nos dice si el índice aleatorio calculado fue el mismo. Para usar este método:

String[] s1 = {"a", "b", "c", "d", "e", "f"};
String[] s2 = {"1", "2", "3", "4", "5", "6"};

RandomElement<String> r1 = new RandomElement<String>(s1);
RandomElement<String> r2 = new RandomElement<String>(s2);

System.out.println(r1.sameIndex(r2));

El código funciona, sólo devolverá verdadero si tanto el objeto r1 como en r2, el índice tiene el mismo valor. Sin embargo, este otro código fallara:

String[] s1 = {"a", "b", "c", "d", "e", "f"};
Integer[] s2 = {1,2,3,4,5,6};
RandomElement<String> r1 = new RandomElement<String>(s1);
RandomElement<Integer> r2 = new RandomElement<Integer>(s2);

System.out.println(r1.sameIndex(r2));

El error ocurre en tiempo de compilación. La razón, que en la línea remarcada, el objeto r1 es ya de tipo RandomElement<String> por lo que el método sameIndex sólo puede aceptar objetos de tipo RandomElement<String>, ya que el genérico T se tradujo como STring. El problema está en que dicho método tiene que poder aceptar cualquier tipo de objeto de tipo RandomElement.

Para ello se usa el signo <?> de ese modo indicamos la posibilidad de aceptar cualquier tipo de clase con genérico. Es decir el método se reescribiría así:

public boolean sameIndex(RandomElement<?> random){
    return random.getIndex() == index;
}

De esa forma se indica que random es un objeto de tipo RandomElement tenga el tipo que tenga el genérico.

Incluso se puede delimitar el genéricos:

public boolean sameIndex(RandomElement<? extends Number> random){
    return random.getIndex() == index;
}

Ahora el método acepta cualquier tipo de RandomElement pero siempre y cuando el tipo genérico forme parte de la herencia Number