Estaba explorando la fuente de Java 8 y encontré esta parte particular del código muy sorprendente:
//defined in IntPipeline.java
@Override
public final OptionalInt reduce(IntBinaryOperator op) {
return evaluate(ReduceOps.makeInt(op));
}
@Override
public final OptionalInt max() {
return reduce(Math::max); //this is the gotcha line
}
//defined in Math.java
public static int max(int a, int b) {
return (a >= b) ? a : b;
}
¿Es Math::max
algo así como un puntero de método? ¿Cómo funciona un normalstatic
se convierte métodoIntBinaryOperator
?
TestingLambda$$Lambda$2/8460669
yTestingLambda$$Lambda$3/11043253
fueron creados en dos invocaciones.Respuestas:
Por lo general, uno llamaría al
reduce
método de laMath.max(int, int)
siguiente manera:Eso requiere mucha sintaxis para solo llamar
Math.max
. Ahí es donde entran en juego las expresiones lambda. Desde Java 8 está permitido hacer lo mismo de una manera mucho más corta:¿Como funciona esto? El compilador de Java "detecta" que desea implementar un método que acepte dos
int
sy devuelva unoint
. Esto es equivalente a los parámetros formales del único método de interfazIntBinaryOperator
(el parámetro del métodoreduce
que desea llamar). Entonces el compilador hace el resto por usted, solo asume que desea implementarIntBinaryOperator
.Pero como
Math.max(int, int)
cumple con los requisitos formales deIntBinaryOperator
, se puede usar directamente. Debido a que Java 7 no tiene ninguna sintaxis que permita pasar un método en sí mismo como argumento (solo puede pasar resultados de métodos, pero nunca referencias de métodos), la::
sintaxis se introdujo en Java 8 para hacer referencia a métodos:Tenga en cuenta que esto será interpretado por el compilador, no por la JVM en tiempo de ejecución. Aunque produce diferentes códigos de bytes para los tres fragmentos de código, son semánticamente iguales, por lo que los dos últimos pueden considerarse versiones cortas (y probablemente más eficientes) de
IntBinaryOperator
implementación anterior.(Ver también Traducción de expresiones lambda )
fuente
::
se llama Método de referencia. Básicamente es una referencia a un único método. Es decir, se refiere a un método existente por su nombre.Explicación breve : a
continuación se muestra un ejemplo de una referencia a un método estático:
square
puede pasarse como referencias a objetos y activarse cuando sea necesario. De hecho, puede usarse tan fácilmente como una referencia a métodos "normales" de objetos comostatic
los. Por ejemplo:Function
arriba es una interfaz funcional . Para comprender completamente::
, también es importante comprender las interfaces funcionales. Claramente, una interfaz funcional es una interfaz con un solo método abstracto.Ejemplos de interfaces funcionales incluyen
Runnable
,Callable
, yActionListener
.Function
anterior es una interfaz funcional con un solo método:apply
. Toma un argumento y produce un resultado.La razón por la que los
::
s son increíbles es que :Por ejemplo, en lugar de escribir el cuerpo lambda
Simplemente puedes hacer
En tiempo de ejecución, estos dos
square
métodos se comportan exactamente igual que el otro. El bytecode puede o no ser el mismo (aunque, para el caso anterior, se genera el mismo bytecode; compile lo anterior y verifique conjavap -c
).El único criterio importante para satisfacer es: el método que proporcione debe tener una firma similar al método de la interfaz funcional que utiliza como referencia de objeto .
Lo siguiente es ilegal:
square
espera un argumento y devuelve adouble
. Elget
método en Proveedor devuelve un valor pero no toma un argumento. Por lo tanto, esto da como resultado un error.Una referencia de método se refiere al método de una interfaz funcional. (Como se mencionó, las interfaces funcionales solo pueden tener un método cada una).
Algunos ejemplos más: el
accept
método en Consumer toma una entrada pero no devuelve nada.Arriba,
getRandom
no toma argumentos y devuelve adouble
. Por lo tanto, se puede utilizar cualquier interfaz funcional que satisfaga los criterios de: no tomar argumento y volverdouble
.Otro ejemplo:
En caso de tipos parametrizados :
Las referencias de métodos pueden tener diferentes estilos, pero fundamentalmente todas significan lo mismo y simplemente se pueden visualizar como lambdas:
ClassName::methName
)instanceRef::methName
)super::methName
)ClassName::methName
)ClassName::new
)TypeName[]::new
)Para obtener más información, consulte http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html .
fuente
Si eso es verdad. El
::
operador se utiliza para referenciar métodos. Entonces, uno puede extraer métodos estáticos de las clases usándolo o métodos de objetos. El mismo operador se puede utilizar incluso para constructores. Todos los casos mencionados aquí se ejemplifican en el ejemplo de código a continuación.La documentación oficial de Oracle se puede encontrar aquí .
Puede tener una mejor visión general de los cambios de JDK 8 en este artículo. En la sección de referencia Método / Constructor también se proporciona un ejemplo de código:
fuente
method(Math::max);
es invocación y la definición del método sería similarpublic static void method(IntBinaryOperator op){System.out.println(op.applyAsInt(1, 2));}
. Así es como se usa.Parece que es un poco tarde, pero aquí están mis dos centavos. Una expresión lambda se usa para crear métodos anónimos. No hace nada más que llamar a un método existente, pero es más claro referirse al método directamente por su nombre. Y la referencia de método nos permite hacerlo utilizando el operador de referencia de método
::
.Considere la siguiente clase simple donde cada empleado tiene un nombre y grado.
Supongamos que tenemos una lista de empleados devueltos por algún método y queremos clasificar a los empleados por su grado. Sabemos que podemos hacer uso de la clase anónima como:
donde getDummyEmployee () es un método como:
Ahora sabemos que Comparator es una interfaz funcional. Una interfaz funcional es la que tiene exactamente un método abstracto (aunque puede contener uno o más métodos predeterminados o estáticos). La expresión Lambda proporciona la implementación de
@FunctionalInterface
modo que una interfaz funcional solo puede tener un método abstracto. Podemos usar la expresión lambda como:Parece todo bien, pero ¿y si la clase
Employee
también proporciona un método similar?En este caso, usar el nombre del método en sí será más claro. Por lo tanto, podemos referirnos directamente al método utilizando la referencia del método como:
Según los documentos, hay cuatro tipos de referencias de métodos:
fuente
::
es un nuevo operador incluido en Java 8 que se usa para referir un método de una clase existente. Puede consultar métodos estáticos y métodos no estáticos de una clase.Para referirse a métodos estáticos, la sintaxis es:
Para hacer referencia a métodos no estáticos, la sintaxis es
Y
El único requisito previo para referir un método es que ese método exista en una interfaz funcional, que debe ser compatible con la referencia del método.
Las referencias de métodos, cuando se evalúan, crean una instancia de la interfaz funcional.
Encontrado en: http://www.speakingcs.com/2014/08/method-references-in-java-8.html
fuente
Esta es una referencia de método en Java 8. La documentación de Oracle está aquí .
Como se indica en la documentación ...
fuente
:: El operador se introdujo en Java 8 para referencias de métodos. Una referencia de método es la sintaxis abreviada para una expresión lambda que ejecuta solo UN método. Aquí está la sintaxis general de una referencia de método:
Sabemos que podemos usar expresiones lambda en lugar de usar una clase anónima. Pero a veces, la expresión lambda es realmente solo una llamada a algún método, por ejemplo:
Para aclarar el código, puede convertir esa expresión lambda en una referencia de método:
fuente
El :: se conoce como referencias de método. Digamos que queremos llamar a un método CalculatePrice de la clase Compra. Entonces podemos escribirlo como:
También se puede ver como una forma corta de escribir la expresión lambda porque las referencias de métodos se convierten en expresiones lambda.
fuente
Encontré esta fuente muy interesante.
De hecho, es la Lambda la que se convierte en un doble colon . El doble colon es más legible. Seguimos esos pasos:
PASO 1:
PASO 2:
PASO 3:
fuente
Person::getAge()
debería serPerson::getAge
.return reduce(Math::max);
es DIFERENTE areturn reduce(max());
Pero significa, algo como esto:
Puede guardar 47 pulsaciones de teclas si escribe así
fuente
Dado que muchas respuestas aquí explicaron bien el
::
comportamiento, adicionalmente me gustaría aclarar que el::
operador no necesita tener exactamente la misma firma que la interfaz funcional de referencia si se usa para variables de instancia . Supongamos que necesitamos un BinaryOperator que tenga el tipo de TestObject . De manera tradicional se implementa así:Como puede ver en la implementación anónima, requiere dos argumentos TestObject y también devuelve un objeto TestObject. Para satisfacer esta condición utilizando el
::
operador, podemos comenzar con un método estático:y luego llame a:
Ok, se compiló bien. ¿Qué pasa si necesitamos un método de instancia? Permite actualizar TestObject con el método de instancia:
Ahora podemos acceder a la instancia de la siguiente manera:
Este código se compila bien, pero debajo de uno no:
Mi eclipse me dice "No se puede hacer una referencia estática al método no estático testInstance (TestObject, TestObject) del tipo TestObject ..."
Es justo que sea un método de instancia, pero si lo sobrecargamos de la
testInstance
siguiente manera:Y llama:
El código simplemente se compilará bien. Porque llamará
testInstance
con un solo parámetro en lugar de uno doble. Ok, entonces, ¿qué pasó con nuestros dos parámetros? Permite imprimir y ver:Lo que dará salida:
Ok, entonces JVM es lo suficientemente inteligente como para llamar a param1.testInstance (param2). ¿Podemos usar
testInstance
desde otro recurso pero no TestObject, es decir:Y llama:
Simplemente no se compilará y el compilador dirá: "El tipo TestUtil no define testInstance (TestObject, TestObject)" . Entonces el compilador buscará una referencia estática si no es del mismo tipo. Ok, ¿y el polimorfismo? Si eliminamos los modificadores finales y agregamos nuestro SubTestObject clase :
Y llama:
No se compilará también, el compilador seguirá buscando referencias estáticas. Pero el siguiente código se compilará bien ya que está pasando una prueba:
fuente
En java-8 Streams Reducer en trabajos simples es una función que toma dos valores como entrada y devuelve el resultado después de algunos cálculos. este resultado se alimenta en la próxima iteración.
en el caso de Math: función max, el método sigue devolviendo el máximo de dos valores pasados y al final tiene el mayor número disponible.
fuente
En tiempo de ejecución se comportan exactamente igual. El código de bytes puede / no ser el mismo (para el caso anterior, genera el mismo código de bytes (compile arriba y verifique javaap -c;))
En tiempo de ejecución, se comportan exactamente igual. Método (math :: max) ;, genera la misma matemática (compilar arriba y verificar javap -c;))
fuente
En versiones anteriores de Java, en lugar de "::" o lambd, puede usar:
O pasando al método:
fuente
Así que veo aquí toneladas de respuestas que son francamente complicadas, y eso es un eufemismo.
La respuesta es bastante simple: :: se llama un Método Referencias https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html
Por lo tanto, no copiaré y pegaré, en el enlace, puede encontrar toda la información si se desplaza hacia abajo a la tabla.
Ahora, echemos un vistazo a lo que es un método de referencias:
A :: B sustituye de alguna manera la siguiente expresión lambda en línea : (parámetros ...) -> AB (parámetros ...)
Para correlacionar esto con sus preguntas, es necesario comprender una expresión java lambda. Lo cual no es difícil.
Una expresión lambda en línea es similar a una interfaz funcional definida (que es una interfaz que no tiene más y no menos de 1 método) . Echemos un vistazo a lo que quiero decir:
InterfaceX debe ser una interfaz funcional. Cualquier interfaz funcional, lo único importante de InterfaceX para ese compilador es que defina el formato:
InterfaceX puede ser cualquiera de esto:
o esto
o más genérico:
Tomemos el primer caso presentado y la expresión lambda en línea que definimos anteriormente.
Antes de Java 8, podría haberlo definido de manera similar de esta manera:
Funcionalmente, es lo mismo. La diferencia es más en cómo el compilador percibe esto.
Ahora que echamos un vistazo a la expresión lambda en línea, volvamos a Referencias de métodos (: :). Digamos que tienes una clase como esta:
Dado que el método anyFunctions tiene los mismos tipos que InterfaceX callMe , podemos equivaler esos dos con una Referencia de método.
Podemos escribirlo así:
y eso es equivalente a esto:
Una cosa interesante y una ventaja de las Referencias de métodos es que al principio, hasta que las asignes a variables, no tienen tipo. Por lo tanto, puede pasarlos como parámetros a cualquier interfaz funcional de aspecto equivalente (tiene los mismos tipos definidos). Que es exactamente lo que pasa en tu caso
fuente
Las respuestas anteriores son bastante completas sobre qué
::
método de referencia hace. En resumen, proporciona una forma de referirse a un método (o constructor) sin ejecutarlo, y cuando se evalúa, crea una instancia de la interfaz funcional que proporciona el contexto de tipo de destino.A continuación se muestran dos ejemplos para encontrar un objeto con el valor máximo en un
ArrayList
WITH y SIN el uso del::
método de referencia. Las explicaciones están en los comentarios a continuación.SIN el uso de
::
Con el uso de
::
fuente
El
::
operador de dos puntos dobles, es decir , se introduce en Java 8 como referencia de método . La referencia de método es una forma de expresión lambda que se utiliza para referirse al método existente por su nombre.classname :: methodName
ex:-
stream.forEach(element -> System.out.println(element))
Mediante el uso de doble colon
::
stream.forEach(System.out::println(element))
fuente