La última mejora que podemos llevar a cabo va a permitir facilitar el uso del sorter desde el código cliente. Vamos a ver como. Si nuestro sorter ordena listas de objetos, como por ejemplo manzanas, y estos objetos se distinguen por poseer una serie de caracteristicas o propiedades, en el caso de las manzanas color y peso; podemos hacer que el codigo cliente solo tenga que elegir cual es la caracteristica de entre todas las que tiene una clase de ojetos para ordenarlos en base a esta propiedad elegida.
Qué caracteristica vamos a escoger para ordenar sera la estrategia, que llamaremos selector
public interface Selector<TSource, TResult> {
TResult select(TSource item);
}
Como vemos está parametrizado para dos tipos, el tipo de entrada TSource y el tipo de resultado. En nuestro caso TSource será el tipo del elemento, Manzana y TResult será el tipo de la caracteristica elegida, String si es el color o double si se trata de el peso.
La interfaz define un único método select que recibe un TSource y devuelve un TResult. En el caso de seleccionar el color de las manzanas Apple y String respectivamente.
Veamos 2 declaraciones de estrategias en el método main de la clase principal del programa.
//Declara las dos estrategias de seleccion Selector<Apple, String> colorSelector = (Apple a) -> a.getColor(); Selector<Apple, Double> weightSelector = a -> a.getWeight();
En el segundo selector, en la expresión lambda no hemos especificado el tipo de la entrada a a la expresión, el compilador la infiere observando que la expresión lambda se asigna a la variable weightSelector, que es de tipo Selector, y como el método select de la interfaz es TResult select(TSource item) y dado que el selector ha sido definido de forma geerica Selector queda claro para el compilador que TSource es Apple.
Veamos ahora como queda la nueva sobrecarga del método sort que acepta un selector.
public static <TSource, TResult extends Comparable<TResult>> void sort(
TSource[] items, Selector<TSource, TResult> selector) {
Comparer<TSource> comparer =
(x, y) -> selector.select(x).compareTo(selector.select(y));
sort(items, comparer);
}
Lo primero que observamos, es que usa el selector para definir un comparador mediante una expresión lambda formada a partir de llamadas al selector y luego delega en el método sort anterior que utilizaba estrategias de comparación para su funcionamiento parsandole como argumento ese comparador construido internamente.
La firma del método es un poco complicada por el hecho de que se está aplicando una restricción al parámetro de clase TResult: TResult extends Comparable. Como TResult no sirve cualquier tipo de clase, si no solamente aquellas que sean de tipo Comparable o sus subclases. Esto garantiza que dado que TResult implementa Comparable entonces se podrá llamar al método compareTo, que es justo lo que hace la expresión lambda con el valor devuelto por el selector.
La expresion lambda selecciona mediante el selector la misma característica en ambos objetos x e y, y puesto que queda garantizado que la característica es de tipo Comparable o una subclase se puede usar el método compareTo con ellas.
Entonces, ¿óomo será el código para ordenar por color o por peso?
Sorter.sort(apples, weightSelector); printApples(); Sorter.sort(apples, colorSelector); printApples();
Incluso podemos ser más directos y no crear variables intermedias escribiendo la expresión lambda directamente en la llamada. Es como si un comportamiento también fuese considerado un dato de entrada en la llamada.
Sorter.sort(apples, a -> a.getColor()); printApples();
En este caso, como la caracteristica la devuelve un método, el getter, podemos utilizar la nueva característica de java consistente la capacidad para asignar a variables de interfaz referencias directas a nombres de métodos.
Sorter.sort(apples, Apple::getWeight); printApples();
El método getWeight() es un método de instancia, lo que quiere decir que en realidad recibe un argumento más en su llamada además de los que explicitamente se definen en su firma. Ese argumento adicional es la instancia que lo invoca, es decir, la llamada será x.getWeight() o y.getWeight(), etc y esa x o esa y que son referencias a objetos Apple son «argumentos» de entrada en la llamada. Esto es lo que hace que x.getWeight() devuelva el peso de la manzana x y y.getWeight() el peso de la manzana y, x e y «parametrizan» el resultado de la llamada.
Es por eso que la expresión Apple::getWeight es válida para asignar a una interfaz Selector cuyo método es TResult select(TSource source) El método select necesita un argumento, aunque getWeight() no recibe ninguno, pero eso es de forma explicita porque al ser un método de instancia si que recibe la refencia al objeto que lo invoca x si hicieramos x.getWeight(). Por tanto, a efectos prácticos es como si se sustituyera por una versión estática getWeight(Apple a) que ya es compatible con el método select de la interfaz.