parameter - :: Operador(doble colon) en Java 8
method and constructor references in java 8 (15)
Así que veo aquí un montón de respuestas que son francamente complicadas, y eso es un eufemismo.
La respuesta es bastante simple: :: se llama Referencias de métodos https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html
Por lo tanto, no copiaré ni pegaré, en el enlace, puede encontrar toda la información si se desplaza hacia la tabla.
Ahora, veamos brevemente qué es un método de referencias:
A :: B sustituye de alguna manera la siguiente expresión lambda en línea : (params ...) -> AB (params ...)
Para correlacionar esto con sus preguntas, es necesario entender una expresión Java Lambda. Lo que 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 ni menos de 1 método) . Echemos un breve vistazo a lo que quiero decir:
InterfaceX f = (x) -> x*x;
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:
interface InterfaceX
{
public Integer callMe(Integer x);
}
o esto
interface InterfaceX
{
public Double callMe(Integer x);
}
o más genérico:
interface InterfaceX<T,U>
{
public T callMe(U x);
}
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 la misma manera:
InterfaceX o = new InterfaceX(){
public int callMe (int x, int y)
{
return x*x;
} };
Funcionalmente, es lo mismo. La diferencia está más en cómo el compilador percibe esto.
Ahora que echamos un vistazo a la expresión lambda en línea, volvamos a las Referencias de métodos (: :). Digamos que tienes una clase como esta:
class Q {
public static int anyFunction(int x)
{
return x+5;
}
}
Dado que el método anyFunctions tiene los mismos tipos que InterfaceX callMe , podemos equiparar esos dos con una referencia de método.
Podemos escribirlo así:
InterfaceX o = Q::anyFunction;
y eso es equivalente a esto:
InterfaceX o = (x) -> Q.anyFunction(x);
Una cosa interesante y una ventaja de las Referencias de métodos es que, al principio, hasta que las asigna a las 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.
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 como un puntero de método? ¿Cómo se convierte un método static
normal a IntBinaryOperator
?
Dado que muchas respuestas aquí explican bien el comportamiento: además, me gustaría aclarar que ::
operador no necesita tener exactamente la misma firma que la Interfaz funcional de referencia si se usa para variables de ejemplo . Supongamos que necesitamos un BinaryOperator que tiene el tipo de TestObject . De manera tradicional se implementa así:
BinaryOperator<TestObject> binary = new BinaryOperator<TestObject>() {
@Override
public TestObject apply(TestObject t, TestObject u) {
return t;
}
};
Como se ve en la implementación anónima, se requieren dos argumentos de TestObject y también se devuelve un objeto TestObject. Para satisfacer esta condición utilizando ::
operator, podemos comenzar con un método estático:
public class TestObject {
public static final TestObject testStatic(TestObject t, TestObject t2){
return t;
}
}
y luego llamar:
BinaryOperator<TestObject> binary = TestObject::testStatic;
Ok se compiló bien. ¿Qué pasa si necesitamos un método de instancia? Permite actualizar TestObject con método de instancia:
public class TestObject {
public final TestObject testInstance(TestObject t, TestObject t2){
return t;
}
public static final TestObject testStatic(TestObject t, TestObject t2){
return t;
}
}
Ahora podemos acceder a la instancia de la siguiente manera:
TestObject testObject = new TestObject();
BinaryOperator<TestObject> binary = testObject::testInstance;
Este código compila bien, pero debajo de uno no:
BinaryOperator<TestObject> binary = TestObject::testInstance;
Mi eclipse me dice "No se puede hacer una referencia estática al método no estático testInstance (TestObject, TestObject) del tipo TestObject ..."
Suficientemente justo es un método de instancia, pero si sobrecargamos testInstance
como a continuación:
public class TestObject {
public final TestObject testInstance(TestObject t){
return t;
}
public final TestObject testInstance(TestObject t, TestObject t2){
return t;
}
public static final TestObject testStatic(TestObject t, TestObject t2){
return t;
}
}
Y llama:
BinaryOperator<TestObject> binary = TestObject::testInstance;
El código simplemente compilará bien. Porque llamará a testInstance
con un solo parámetro en lugar de uno doble. Ok, ¿qué pasó con nuestros dos parámetros? Permite imprimir y ver:
public class TestObject {
public TestObject() {
System.out.println(this.hashCode());
}
public final TestObject testInstance(TestObject t){
System.out.println("Test instance called. this.hashCode:"
+ this.hashCode());
System.out.println("Given parameter hashCode:" + t.hashCode());
return t;
}
public final TestObject testInstance(TestObject t, TestObject t2){
return t;
}
public static final TestObject testStatic(TestObject t, TestObject t2){
return t;
}
}
Que saldrá:
1418481495
303563356
Test instance called. this.hashCode:1418481495
Given parameter hashCode:303563356
Ok, entonces JVM es lo suficientemente inteligente como para llamar a param1.testInstance (param2). ¿Podemos usar testInstance
de otro recurso pero no TestObject, es decir:
public class TestUtil {
public final TestObject testInstance(TestObject t){
return t;
}
}
Y llama:
BinaryOperator<TestObject> binary = TestUtil::testInstance;
Simplemente no se compilará y el compilador le dirá: "El tipo TestUtil no define testInstance (TestObject, TestObject)" . Entonces el compilador buscará una referencia estática si no es del mismo tipo. Ok, ¿qué pasa con el polimorfismo? Si eliminamos los modificadores finales y agregamos nuestra clase SubTestObject :
public class SubTestObject extends TestObject {
public final TestObject testInstance(TestObject t){
return t;
}
}
Y llama:
BinaryOperator<TestObject> binary = SubTestObject::testInstance;
Tampoco se compilará, el compilador seguirá buscando referencias estáticas. Pero a continuación el código se compilará bien, ya que está pasando es una prueba:
public class TestObject {
public SubTestObject testInstance(Object t){
return (SubTestObject) t;
}
}
BinaryOperator<TestObject> binary = TestObject::testInstance;
* Solo estoy estudiando, así que me he dado cuenta por probar y ver, siéntase libre de corregirme si estoy equivocado
El :: se conoce como referencias de método. Digamos que queremos llamar a un método calculado de la clase Compra. Entonces podemos escribirlo como:
Purchase::calculatePrice
También se puede ver como una forma corta de escribir la expresión lambda, ya que las referencias de los métodos se convierten en expresiones lambda.
En el tiempo de ejecución se comportan exactamente igual. El bytecode puede / no ser el mismo (para el caso anterior, genera el mismo bytecode (complie anterior y marque javaap -c;))
En el tiempo de ejecución se comportan exactamente igual.método (math :: max) ;, genera el mismo math (complie anterior y compruebe javap -c;))
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 algún cálculo. Este resultado se alimenta en la siguiente iteración.
en el caso de Math: max function, el método continúa devolviendo max de dos valores pasados y al final tienes el mayor número en la mano.
En versiones anteriores de Java, en lugar de "::" o lambd, puede usar:
public interface Action {
void execute();
}
public class ActionImpl implements Action {
@Override
public void execute() {
System.out.println("execute with ActionImpl");
}
}
public static void main(String[] args) {
Action action = new Action() {
@Override
public void execute() {
System.out.println("execute with anonymous class");
}
};
action.execute();
//or
Action actionImpl = new ActionImpl();
actionImpl.execute();
}
O pasar al método:
public static void doSomething(Action action) {
action.execute();
}
Esta es una referencia de método en Java 8. La documentación de Oracle está here .
Como se indica en la documentación ...
La referencia de método Person :: compareByAge es una referencia a un método estático.
El siguiente es un ejemplo de una referencia a un método de instancia de un objeto en particular:
class ComparisonProvider {
public int compareByName(Person a, Person b) {
return a.getName().compareTo(b.getName());
}
public int compareByAge(Person a, Person b) {
return a.getBirthday().compareTo(b.getBirthday());
}
}
ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);
La referencia del método myComparisonProvider :: compareByName invoca el método compareByName que forma parte del objeto myComparisonProvider. El JRE infiere los argumentos de tipo de método, que en este caso son (Persona, Persona).
Generalmente, uno llamaría al método de reduce
usando Math.max(int, int)
siguiente manera:
reduce(new IntBinaryOperator() {
int applyAsInt(int left, int right) {
return Math.max(left, right);
}
});
Eso requiere mucha sintaxis para simplemente llamar a 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:
reduce((int left, int right) -> Math.max(left, right));
¿Como funciona esto? El compilador java "detecta" que desea implementar un método que acepte dos int
sy devuelva uno int
. Esto es equivalente a los parámetros formales del único método de interfaz IntBinaryOperator
(el parámetro de reduce
de método que desea llamar). Así que el compilador hace el resto por usted, solo asume que desea implementar IntBinaryOperator
.
Pero como Math.max(int, int)
cumple con los requisitos formales de IntBinaryOperator
, se puede utilizar directamente. Como Java 7 no tiene ninguna sintaxis que permita que un método se pase como argumento (solo puede pasar los resultados de los métodos, pero nunca las referencias de los métodos), la sintaxis ::
se introdujo en Java 8 a los métodos de referencia:
reduce(Math::max);
Tenga en cuenta que esto será interpretado por el compilador, no por la JVM en tiempo de ejecución. Aunque produce códigos de bytes diferentes 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 la implementación IntBinaryOperator
anterior.
(Ver también Traducción de expresiones Lambda ).
He encontrado esta fuente muy interesante.
De hecho, es la lambda que se convierte en un doble colon . El doble colon es más legible. Seguimos esos pasos:
PASO 1:
// We create a comparator of two persons
Comparator c = (Person p1, Person p2) -> p1.getAge().compareTo(p2.getAge());
PASO 2:
// We use the interference
Comparator c = (p1, p2) -> p1.getAge().compareTo(p2.getAge());
PASO 3:
// The magic
Comparator c = Comparator.comparing(Person::getAge());
Parece que es un poco tarde pero aquí están mis dos centavos. Una expresión lambda se utiliza 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 usando el operador de referencia de método ::
.
Considere la siguiente clase simple donde cada empleado tiene un nombre y una calificación.
public class Employee {
private String name;
private String grade;
public Employee(String name, String grade) {
this.name = name;
this.grade = grade;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGrade() {
return grade;
}
public void setGrade(String grade) {
this.grade = grade;
}
}
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:
List<Employee> employeeList = getDummyEmployees();
// Using anonymous class
employeeList.sort(new Comparator<Employee>() {
@Override
public int compare(Employee e1, Employee e2) {
return e1.getGrade().compareTo(e2.getGrade());
}
});
donde getDummyEmployee () es un método como:
private static List<Employee> getDummyEmployees() {
return Arrays.asList(new Employee("Carrie", "C"),
new Employee("Farhan", "F"),
new Employee("Brian", "B"),
new Employee("Donald", "D"),
new Employee("Adam", "A"),
new Employee("Evan", "E")
);
}
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). Entonces podemos usar la expresión lambda como:
employeeList.sort((e1,e2) -> e1.getGrade().compareTo(e2.getGrade())); // lambda exp
Parece que todo está bien, pero ¿qué pasa si la clase Employee
también proporciona un método similar:
public class Employee {
private String name;
private String grade;
// getter and setter
public static int compareByGrade(Employee e1, Employee e2) {
return e1.grade.compareTo(e2.grade);
}
}
En este caso el uso del 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:
employeeList.sort(Employee::compareByGrade); // method reference
Según los documentos, hay cuatro tipos de referencias de métodos:
+----+-------------------------------------------------------+--------------------------------------+
| | Kind | Example |
+----+-------------------------------------------------------+--------------------------------------+
| 1 | Reference to a static method | ContainingClass::staticMethodName |
+----+-------------------------------------------------------+--------------------------------------+
| 2 |Reference to an instance method of a particular object | containingObject::instanceMethodName |
+----+-------------------------------------------------------+--------------------------------------+
| 3 | Reference to an instance method of an arbitrary object| ContainingType::methodName |
| | of a particular type | |
+----+-------------------------------------------------------+--------------------------------------+
| 4 |Reference to a constructor | ClassName::new |
+------------------------------------------------------------+--------------------------------------+
Si eso es verdad. El operador ::
se utiliza para referenciar métodos. Por lo tanto, uno puede extraer métodos estáticos de las clases mediante su uso o métodos de objetos. El mismo operador puede ser usado incluso para constructores. Todos los casos mencionados aquí se ilustran en el ejemplo de código a continuación.
La documentación oficial de Oracle se puede encontrar here .
Puede tener una mejor visión general de los cambios de JDK 8 en this artículo. En la sección Referenciación de método / constructor , también se proporciona un ejemplo de código:
interface ConstructorReference {
T constructor();
}
interface MethodReference {
void anotherMethod(String input);
}
public class ConstructorClass {
String value;
public ConstructorClass() {
value = "default";
}
public static void method(String input) {
System.out.println(input);
}
public void nextMethod(String input) {
// operations
}
public static void main(String... args) {
// constructor reference
ConstructorReference reference = ConstructorClass::new;
ConstructorClass cc = reference.constructor();
// static method reference
MethodReference mr = cc::method;
// object method reference
MethodReference mr2 = cc::nextMethod;
System.out.println(cc.value);
}
}
::
es un nuevo operador incluido en Java 8 que se usa para referirse a un método de una clase existente. Puede referirse a métodos estáticos y métodos no estáticos de una clase.
Para los métodos estáticos de referencia, la sintaxis es:
ClassName :: methodName
Para los métodos no estáticos de referencia, la sintaxis es
objRef :: methodName
Y
ClassName :: methodName
El único requisito previo para recomendar un método es que el método exista en una interfaz funcional, que debe ser compatible con la referencia del método.
Las referencias de los métodos, cuando se evalúan, crean una instancia de la interfaz funcional.
Se encuentra en: http://www.speakingcs.com/2014/08/method-references-in-java-8.html
::
se llama referencia de método. Es básicamente una referencia a un solo método. Es decir, se refiere a un método existente por nombre.
Explicación corta :
A continuación se muestra un ejemplo de una referencia a un método estático:
class Hey {
public static double square(double num){
return Math.pow(num, 2);
}
}
Function<Double, Double> square = Hey::square;
double ans = square.apply(23d);
square
se puede pasar alrededor de las referencias de objetos y activarse cuando sea necesario. De hecho, se puede usar tan fácilmente como una referencia a los métodos "normales" de objetos como los static
. Por ejemplo:
class Hey {
public double square(double num) {
return Math.pow(num, 2);
}
}
Hey hey = new Hey();
Function<Double, Double> square = hey::square;
double ans = square.apply(23d);
Function
anterior es una interfaz funcional . Para comprender completamente ::
, es importante entender también las interfaces funcionales. Claramente, una interfaz funcional es una interfaz con solo un método abstracto.
Ejemplos de interfaces funcionales incluyen Runnable
, Callable
y ActionListener
.
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 ::
s son impresionantes es that :
Las referencias de método son expresiones que tienen el mismo tratamiento que las expresiones lambda (...), pero en lugar de proporcionar un cuerpo de método, se refieren a un método existente por su nombre.
Por ejemplo, en lugar de escribir el cuerpo lambda.
Function<Double, Double> square = (Double x) -> x * x;
Simplemente puedes hacer
Function<Double, Double> square = Hey::square;
En el tiempo de ejecución, estos dos métodos square
comportan exactamente igual entre sí. El bytecode puede o no ser el mismo (aunque, para el caso anterior, se genera el mismo bytecode; compile lo anterior y verifique con javap -c
).
El único criterio importante que debe 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 de abajo es ilegal:
Supplier<Boolean> p = Hey::square; // illegal
square
espera un argumento y devuelve un double
. El método de get
en Supplier espera un argumento pero no devuelve nada. Por lo tanto, esto se traduce en 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).
Algunos ejemplos más: el método de accept
en Consumer toma una entrada pero no devuelve nada.
Consumer<Integer> b1 = System::exit; // void exit(int status)
Consumer<String[]> b2 = Arrays::sort; // void sort(Object[] a)
Consumer<String> b3 = MyProgram::main; // void main(String... args)
class Hey {
public double getRandom() {
return Math.random();
}
}
Callable<Double> call = hey::getRandom;
Supplier<Double> call2 = hey::getRandom;
DoubleSupplier sup = hey::getRandom;
// Supplier is functional interface that takes no argument and gives a result
Arriba, getRandom
no toma argumentos y devuelve un double
. Por lo tanto, se puede utilizar cualquier interfaz funcional que satisfaga los criterios de: no tomar ningún argumento y devolver el double
.
Otro ejemplo:
Set<String> set = new HashSet<>();
set.addAll(Arrays.asList("leo","bale","hanks"));
Predicate<String> pred = set::contains;
boolean exists = pred.test("leo");
En caso de tipos parametrizados :
class Param<T> {
T elem;
public T get() {
return elem;
}
public void set(T elem) {
this.elem = elem;
}
public static <E> E returnSame(E elem) {
return elem;
}
}
Supplier<Param<Integer>> obj = Param<Integer>::new;
Param<Integer> param = obj.get();
Consumer<Integer> c = param::set;
Supplier<Integer> s = param::get;
Function<String, String> func = Param::<String>returnSame;
Las referencias de los métodos pueden tener diferentes estilos, pero fundamentalmente todos significan lo mismo y pueden visualizarse simplemente como lambdas:
- Un método estático (
ClassName::methName
) - Un método de instancia de un objeto particular (
instanceRef::methName
) - Un súper método de un objeto particular (
super::methName
) - Un método de instancia de un objeto arbitrario de un tipo particular (
ClassName::methName
) - Una referencia de constructor de clase (
ClassName::new
) - Una referencia de constructor de matriz (
TypeName[]::new
)
Para obtener más información, consulte that .
return reduce(Math::max);
NO ES IGUAL return reduce(max());
Pero significa, algo como esto:
IntBinaryOperator myLambda = (a, b)->{(a >= b) ? a : b};//56 keystrokes I had to type -_-
return reduce(myLambda);
Solo puedes guardar 47 pulsaciones de teclado si escribes así
return reduce(Math::max);//Only 9 keystrokes ^_^
:: Operador fue introducido en java 8 para referencias de métodos. Una referencia de método es la sintaxis abreviada de una expresión lambda que ejecuta UN solo método. Aquí está la sintaxis general de una referencia de método:
Object :: methodName
Sabemos que podemos usar expresiones lambda en lugar de usar una clase anónima. Pero a veces, la expresión lambda es solo una llamada a algún método, por ejemplo:
Consumer<String> c = s -> System.out.println(s);
Para que el código sea más claro, puede convertir esa expresión lambda en una referencia de método:
Consumer<String> c = System.out::println;