11 de diciembre de 2020

Limites en genéricos (boundary)

Cuando necesitamos limitar las opciones de un tipo variable podemos establecer los límites adecuados en la declaración del parámetro tipo. En nuestro ejemplo de Identificable vamos a obligar que el tipo T implemente Comparable<T>. El código quedaría así:

public interface Identificable<T extends Comparable<T>> {

    T getId();

}

De esta forma sólo va a poder admitirse tipos que se encuentren dentro de los límites. Podemos probarlo intentando asignar a T un tipo que no sea comparable (aunque sea absurdo prueba con ProductExterno por ejemplo).

Ésto nos permite contar con otros miembros garantizados, en este caso podemos usar el método compareTo(Identificable<T> identificable) y podríamos definir un orden natural para todos los identificables usando el método getId(). El código definitivo sería:

public interface Identificable<T extends Comparable<T>> extends Comparable<Identificable<T>> {

    T getId();

    @Override
    default int compareTo(Identificable<T> identificable) {
        return getId().compareTo(identificable.getId());
    }
}
NOTA: Para que fuera correcto tendríamos que quitar la implementación de Comparable<Vehiculo> en la clase Vehiculo por el concepto type erasure que se menciona en la siguiente entrada.

Los límites no se ciñen a un único tipo, puedes hacer una composición de ellos (pero que sea posible, no puedes decir que sea de más de una clase siendo que no existe la herencia múltiple). En este caso voy a definir un tipo T en un método para que admita Identificable<Long> y Arrancable:

public static void main(String[] args) {
    admiteCoche(new Coche());
}

private static <T extends Identificable<Long> & Arrancable> void admiteCoche(T identificableArrancable) {
    System.out.println(identificableArrancable.getId());
    identificableArrancable.arrancar();
}

En este caso admitirá Coche, pero no Moto (ya que no es Identificable). La definición del tipo parámetro debe hacerse antes del tipo retorno ya que podría usarse T para el retorno y debería estar definido ya en ese caso.

Cuando en el límite quiero añadir una clase debo poner la clase la primera y luego irían todas las interfaces que hagan falta (intenta hacerlo al revés). Éste es un ejemplo pidiendo un Coche y Comerciable, con lo que sólo admitirá un CocheProducto:

private static  <T extends Coche & Comerciable> void admiteCocheProducto(T cocheComerciable) {
    System.out.println(cocheComerciable.getPrecio() + "|" + cocheComerciable.getClass());
}

Como vemos siempre se define un tipo parámetro que termina definiéndose y, dentro de su scope se mantiene fijo (puede ser definido en una clase que implemente un tipo genérico o tomado de un parámetro en un método/constructor). Sin embargo existe la opción de no definir un tipo específico y dejar que se admita sin hacer la declaración de tipo parámetro. Ésto se conoce como wildcard y lo vemos en la siguiente entrada.

No hay comentarios:

Publicar un comentario

Compárteme

Entradas populares