predicados functional funciones expresiones ejercicios ejemplos ejemplo calculo java casting lambda java-8

functional - lambda stream java



Expresiones Java lambda, fundiciĆ³n y comparadores (3)

Estaba buscando el código fuente de Java para la interfaz de Map y encontré este pequeño fragmento de código:

/** * Returns a comparator that compares {@link Map.Entry} in natural order on value. * * <p>The returned comparator is serializable and throws {@link * NullPointerException} when comparing an entry with null values. * * @param <K> the type of the map keys * @param <V> the {@link Comparable} type of the map values * @return a comparator that compares {@link Map.Entry} in natural order on value. * @see Comparable * @since 1.8 */ public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue() { return (Comparator<Map.Entry<K, V>> & Serializable) (c1, c2) -> c1.getValue().compareTo(c2.getValue()); }

De la declaración del método entiendo que este es un método genérico que devuelve un Comparador de un tipo que se deduce de las entradas del mapa que se le pasaron o que se proporciona explícitamente en el método.

Lo que realmente me está tirando es el valor de retorno. Parece que la expresión lambda

(c1, c2) -> c1.getValue().compareTo(c2.getValue());

se Comparator<Map.Entry<K, V>> explícitamente a un Comparator<Map.Entry<K, V>> . ¿Es esto correcto?

También noté que el lanzamiento aparente incluye & Serializable . Nunca antes había visto una interfaz combinada con una clase en un elenco, pero parece que la siguiente es válida en el compilador:

((SubClass & AnInterface) anObject).interfaceMethod();

Aunque lo siguiente no funciona:

public class Foo { public static void main(String[] args) { Object o = new Foo() { public void bar() { System.out.println("nope"); } }; ((Foo & Bar) o).bar(); } } interface Bar { public void bar(); }

Entonces, dos preguntas:

  1. ¿Cómo se supone que el agregar una interfaz a un elenco funciona? ¿Esto solo impone el tipo de devolución del método de una interfaz?

  2. ¿Puedes lanzar una expresión Lambda a un Comparator ? ¿Qué más se puede lanzar como? ¿O es una expresión lambda esencialmente solo un Comparator ? ¿Alguien puede aclarar todo esto?


¿Cómo se supone que el agregar una interfaz a un elenco funciona?

Tiene la sintaxis de un molde, sin embargo, en realidad define el tipo de lambda que está creando a través de la interfaz de tipo. Es decir, no está creando una instancia de un objeto que luego se está proyectando a otro tipo.

¿Esto solo impone el tipo de devolución del método de una interfaz?

Esto realmente define el tipo en que se construirá el lambda como en tiempo de ejecución. Hay una LambdaMetaFactory que obtiene este tipo en tiempo de ejecución y genera código adicional si el tipo incluye Serializable .

¿Puedes lanzar una expresión Lambda a un Comparador?

Solo puede hacer una referencia a un tipo que ya es el objeto. En este caso, usted está definiendo que la lambda que se creará debe ser Comparator . Puede usar cualquier tipo que tenga exactamente un método abstracto.

¿O es una expresión lambda esencialmente solo un Comparador?

Se podría usar el mismo código lambda (copiar + pegar) en diferentes contextos y diferentes interfaces sin cambios. No tiene que ser un Comparator como verá en muchos otros ejemplos en el JDK.

Uno que me parece interesante es el método de count en un Stream .


Aunque Peter ha dado una respuesta excelente, permítanme agregar más para mayor claridad.

Una lambda obtiene su tipo preciso solo durante la inicialización. Esto se basa en el tipo de objetivo. Por ejemplo:

Comparator<Integer> comp = (Integer c1, Integer c2) -> c1.compareTo(c2); BiFunction<Integer, Integer, Integer> c = (Integer c1, Integer c2) -> c1.compareTo(c2); Comparator<Integer> compS = (Comparator<Integer> & Serializable) (Integer c1, Integer c2) -> c1.compareTo(c2);

Por encima de la misma lambda en los 3 casos, pero obtiene su tipo en función del tipo de referencia que ha proporcionado. Por lo tanto, puede establecer el mismo lambda en 3 tipos diferentes en cada caso.

Pero tenga en cuenta que, una vez que se establece el tipo (durante la inicialización), ya no se puede cambiar. Se embebe en el nivel de bytecode. Entonces, obviamente, no puede pasar c a un método que espera Comparator porque una vez inicializado, entonces son como objetos java normales. (Puedes mirar esta clase para jugar y generar lambdas sobre la marcha)

Entonces en caso de:

(Comparator<Map.Entry<K, V>> & Serializable) (c1, c2) -> c1.getValue().compareTo(c2.getValue());

La lambda se inicializa con su tipo de objetivo como Comparador y Serializable. Tenga en cuenta que el tipo de método de devolución es simplemente Comparator , pero dado que Serializable también está inscrito en él durante la inicialización, siempre se puede serializar aunque este mensaje se pierda en la firma del método.

Ahora tenga en cuenta, el lanzamiento a un lambda es diferente de ((Foo & Bar) o).bar(); . En el caso de lambda, está inicializando la lambda con su tipo como el tipo de objetivo declarado. Pero con ((Foo & Bar) o).bar(); , estás escribiendo la variable o ser de Foo y Bar. En el primer caso, está configurando el tipo. En este último caso, ya tiene un tipo y estás probando suerte para lanzarlo a otra cosa. Por lo tanto, en el anterior, arroja ClassCastException porque no puede convertir o en Bar

¿Cómo se supone que el agregar una interfaz a un elenco funciona?

Por objeto, como lo haría normalmente. Para lambda, explicado arriba.

¿Esto solo impone el tipo de devolución del método de una interfaz?

No. Java no tiene tipos estructurales . Entonces no hay un tipo especial basado en el método. Simplemente intenta lanzar o a SO1 y Bar y falla debido a este último

¿Puedes lanzar una expresión Lambda a un Comparador? ¿Qué más se puede lanzar como? ¿O es una expresión lambda esencialmente solo un Comparador? ¿Alguien puede aclarar todo esto?

Como se explicó anteriormente. Una lambda se puede inicializar a cualquier FunctionalInterface en función de qué todas las interfaces son elegibles para esa lambda. En los ejemplos anteriores, obviamente no se puede inicializar (c1, c2) -> c1.compareTo(c2) a un predicado


Según la Especificación del lenguaje Java , el operador de conversión (lo que esté entre paréntesis) puede ser un ReferenceType seguido de uno o más términos de AdditionalBound , es decir, uno o más tipos de interfaz. Además, la especificación también establece que es un error de tiempo de compilación si el tipo de tiempo de compilación del operando nunca se puede convertir al tipo especificado por el operador de conversión de acuerdo con las reglas de la conversión de conversión .

En su caso, Foo no implementa Bar , pero este hecho puede no ser aparente en tiempo de compilación, por lo que obtiene una ClassCastException porque aunque el método definido en la clase anónima tiene la misma firma que la definida en Bar , el objeto no implementa explícitamente Bar . Además, los métodos definidos en clases anónimas están ocultos, a menos que se invoquen en la misma declaración que cuando están definidos, es decir,

new MyClass() { void doSomethingAwesome() { /* ... */ } }.doSomethingAwesome();

funciona, pero esto no:

MyClass awesome = new MyClass() { void doSomethingAwesome() { /* ... */ } }; // undefined method, does not compile! awesome.doSomethingAwesome();