:: (doble colon) operador en Java 8

Estaba explorando la fuente de Java 8 y encontré esta parte 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 se convierte un método static normal a IntBinaryOperator ?

Por lo general, 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 syntax solo por llamar a Math.max . Ahí es donde las expresiones lambda entran en juego. 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 comstackdor de java “detecta” que desea implementar un método que acepte dos int y devuelve uno int . Esto es equivalente a los parámetros formales del único método de la interfaz IntBinaryOperator (el parámetro del método reduce que desea llamar). Así que el comstackdor hace el rest por usted; simplemente asume que desea implementar IntBinaryOperator .

Pero como Math.max(int, int) sí mismo cumple con los requisitos formales de IntBinaryOperator , se puede usar directamente. Como Java 7 no tiene ninguna syntax que permita que un método se pase como argumento (solo se pueden pasar los resultados del método, pero nunca las referencias al método), la syntax :: se introdujo en Java 8 para hacer referencia a los métodos:

 reduce(Math::max); 

Tenga en cuenta que esto será interpretado por el comstackdor, ¡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 la implementación de IntBinaryOperator anterior.

(Véase también Traducción de expresiones lambda )

:: se llama referencia de método Básicamente es una referencia a un único método. Es decir, se refiere a un método existente por su nombre.

Breve explicación
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 square = Hey::square; double ans = square.apply(23d); 

square se puede pasar como referencias de objetos y se puede disparar cuando sea necesario. De hecho, puede usarse tan fácilmente como una referencia a métodos de objetos “normales” como los static . Por ejemplo:

 class Hey { public double square(double num) { return Math.pow(num, 2); } } Hey hey = new Hey(); Function square = hey::square; double ans = square.apply(23d); 

Function anterior es una interfaz funcional . Para comprender completamente :: , es importante comprender las interfaces funcionales también. Claramente, una interfaz funcional es una interfaz con solo un método abstracto.

Los ejemplos de interfaces funcionales incluyen Runnable , Callable y ActionListener .

Function anterior es una interfaz funcional con solo un método: apply . Toma un argumento y produce un resultado.


La razón por la cual :: s son increíbles es que :

Las referencias de métodos son expresiones que tienen el mismo tratamiento que las expresiones lambda (…), pero en lugar de proporcionar un cuerpo de método, hacen referencia a un método existente por su nombre.

Por ejemplo, en lugar de escribir el cuerpo lambda

 Function square = (Double x) -> x * x; 

Puedes simplemente hacer

 Function square = Hey::square; 

En tiempo de ejecución, estos dos métodos square comportan exactamente igual entre sí. El bytecode puede ser o no el mismo (aunque, para el caso anterior, se genera el mismo bytecode, comstackr lo anterior y verificar con javap -c ).

El único criterio importante que debe cumplir 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:

 Supplier p = Hey::square; // illegal 

square espera un argumento y devuelve un double . El método get en Supplier espera un argumento pero no devuelve nada. Por lo tanto, esto resulta en un error.

Una referencia de método se refiere al método de una interfaz funcional. (Como se mencionó, las interfaces funcionales pueden tener solo un método cada una).

Algunos ejemplos más: el método accept en Consumer toma una entrada pero no devuelve nada.

 Consumer b1 = System::exit; // void exit(int status) Consumer b2 = Arrays::sort; // void sort(Object[] a) Consumer b3 = MyProgram::main; // void main(String... args) class Hey { public double getRandom() { return Math.random(); } } Callable call = hey::getRandom; Supplier call2 = hey::getRandom; DoubleSupplier sup = hey::getRandom; // Supplier is functional interface that takes no argument and gives a result 

Arriba, getRandom no toma argumento y devuelve un double . Por lo tanto, cualquier interfaz funcional que satisfaga los criterios de: no tomar ningún argumento y devolver double se puede utilizar.

Otro ejemplo:

 Set set = new HashSet<>(); set.addAll(Arrays.asList("leo","bale","hanks")); Predicate pred = set::contains; boolean exists = pred.test("leo"); 

En el caso de tipos parametrizados :

 class Param { T elem; public T get() { return elem; } public void set(T elem) { this.elem = elem; } public static  E returnSame(E elem) { return elem; } } Supplier> obj = Param::new; Param param = obj.get(); Consumer c = param::set; Supplier s = param::get; Function func = Param::returnSame; 

Las referencias a los métodos pueden tener diferentes estilos, pero fundamentalmente todos significan lo mismo y simplemente se pueden visualizar como lambdas:

  1. Un método estático ( ClassName::methName )
  2. Un método de instancia de un objeto particular ( instanceRef::methName )
  3. Un super::methName de un objeto particular ( super::methName )
  4. Un método de instancia de un objeto arbitrario de un tipo particular ( ClassName::methName )
  5. Una referencia de constructor de clase ( ClassName::new )
  6. Una referencia de constructor de matriz ( TypeName[]::new )

Para obtener más información, consulte http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html .

Si eso es verdad. El operador :: se utiliza para referencia de método. Entonces, uno puede extraer métodos estáticos de las clases al usarlo o métodos de objetos. El mismo operador se puede usar 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:

 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 utiliza para referirse a un método de una clase existente. Puede referir métodos estáticos y métodos no estáticos de una clase.

Para referir métodos estáticos, la syntax es:

 ClassName :: methodName 

Para referir métodos no estáticos, la syntax es

 objRef :: methodName 

Y

 ClassName :: methodName 

El único requisito previo para referir un método es que ese método existe en una interfaz funcional, que debe ser compatible con la referencia del método.

Las referencias de método, 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

Esta es una referencia de método en Java 8. La documentación de Oracle está aquí .

Como se indica en la documentación …

El método de referencia Persona :: 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 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); 

El método de referencia myComparisonProvider :: compareByName invoca el método compareByName que forma parte del objeto myComparisonProvider. El JRE infiere los argumentos del tipo de método, que en este caso son (Persona, Persona).

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 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 hacer eso usando method-reference operator :: .

Considere la siguiente clase simple en la que 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 según su grado. Sabemos que podemos hacer uso de la clase anónima como:

  List employeeList = getDummyEmployees(); // Using anonymous class employeeList.sort(new Comparator() { @Override public int compare(Employee e1, Employee e2) { return e1.getGrade().compareTo(e2.getGrade()); } }); 

donde getDummyEmployee () es algún método como:

 private static List 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 todo bien, pero ¿y 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, usar el nombre del método en sí será más claro. Por lo tanto, podemos referirnos directamente al método utilizando la referencia de 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 | +------------------------------------------------------------+--------------------------------------+ 

:: El operador se introdujo en java 8 para referencias de métodos. Una referencia de método es la syntax abreviada de una expresión lambda que ejecuta solo UN método. Aquí está la syntax 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 c = s -> System.out.println(s); 

Para hacer que el código sea más claro, puede convertir esa expresión lambda en una referencia de método:

 Consumer c = System.out::println; 

The :: se conoce como referencias de método. Digamos que queremos llamar a un método de cálculo de precio de clase Compra. Entonces podemos escribirlo como:

 Purchase::calculatePrice 

También se puede ver como una forma abreviada de escribir la expresión lambda porque las referencias de método 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 de arriba, genera el mismo bytecode (arriba y comprueba javaap -c;))

En tiempo de ejecución se comportan exactamente igual. Method (math :: max) ;, genera la misma matemática (complie arriba y check javap -c;))

return reduce(Math::max); NO ES IGUAL a 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); 

Puedes guardar 47 pulsaciones de teclas si escribes así

 return reduce(Math::max);//Only 9 keystrokes ^_^ 

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 Matemáticas: función máxima, el método sigue devolviendo el máximo de dos valores pasados ​​y al final tiene el número más grande en la mano.

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í:

 BinaryOperator binary = new BinaryOperator() { @Override public TestObject apply(TestObject t, TestObject u) { return t; } }; 

Como ve en la implementación anónima, requiere dos argumentos TestObject y también devuelve un objeto TestObject. Para satisfacer esta condición mediante el uso de :: 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 llama:

 BinaryOperator binary = TestObject::testStatic; 

Ok, compiló bien. ¿Qué pasa si necesitamos un método de instancia? Permite actualizar TestObject con el 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 binary = testObject::testInstance; 

Este código comstack bien, pero debajo de uno no:

 BinaryOperator binary = TestObject::testInstance; 

Mi eclipse me dice “No puedo hacer una referencia estática al método no estático testInstance (TestObject, TestObject) del tipo TestObject …”

Bastante bien es un método de instancia, pero si sobrecargamos testInstance siguiente manera:

 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 binary = TestObject::testInstance; 

El código simplemente comstackrá bien. Porque llamará a testInstance con un solo parámetro en lugar de uno doble. Bien, entonces, ¿qué pasó con nuestros dos parámetros? Vamos a 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 dará salida:

  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 desde otro recurso pero no TestObject, es decir:

 public class TestUtil { public final TestObject testInstance(TestObject t){ return t; } } 

Y llama:

 BinaryOperator binary = TestUtil::testInstance; 

Simplemente no se comstackrá y el comstackdor dirá: “El tipo TestUtil no define testInstance (TestObject, TestObject)” . Entonces el comstackdor buscará una referencia estática si no es del mismo tipo. Ok, ¿y el polymorphism? 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 binary = SubTestObject::testInstance; 

No comstackrá también, el comstackdor buscará la referencia estática. Pero debajo del código comstackrá bien ya que está pasando es una prueba:

 public class TestObject { public SubTestObject testInstance(Object t){ return (SubTestObject) t; } } BinaryOperator binary = TestObject::testInstance; 

* Solo estoy estudiando, así que lo he averiguado con el bash y veo, siéntete libre de corregirme si me equivoco

Encontré esta fuente muy interesante.

De hecho, es la Lambda la que se convierte en un Doble Colón . 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()); 

En las 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 pasando al método:

 public static void doSomething(Action action) { action.execute(); }