Pregunta :: (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 funciona una normal static método se convierte a IntBinaryOperator?


747
2017-11-15 12:46


origen


Respuestas:


Por lo general, uno llamaría al reduce método de uso Math.max(int, int) como sigue:

reduce(new IntBinaryOperator() {
    int applyAsInt(int left, int right) {
        return Math.max(left, right);
    }
});

Eso requiere mucha sintaxis solo para llamar 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 compilador de Java "detecta" que desea implementar un método que acepte dos ints y devuelve uno int. Esto es equivalente a los parámetros formales del único método de interfaz IntBinaryOperator (el parámetro del método reduce Quieres llamar). Entonces, el compilador hace el resto por usted, simplemente asume que desea implementar IntBinaryOperator.

Pero como Math.max(int, int) cumple con los requisitos formales de IntBinaryOperator, se puede usar directamente. Debido a que Java 7 no tiene ninguna sintaxis que permita que un método se pase como un argumento (solo se pueden pasar los resultados del método, pero nunca las referencias al método), el :: 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 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) del IntBinaryOperator implementación arriba!

(Ver también Traducción de Lambda Expressions)


826
2017-11-15 13:08



:: se llama Method Reference. 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<Double, Double> square = Hey::square;
double ans = square.apply(23d);

square se puede pasar como referencia de objeto y disparar cuando sea necesario. De hecho, se puede usar perfectamente como referencia a un método normal de un objeto y no solo static unos.

 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 arriba es un interfaz funcional. Bueno para explicarlo completamente ::, es importante entender la interfaz funcional. Claramente, interfaz de función es una interfaz con solo un método abstracto.

Por ejemplo: Runnable, Callable, ActionListener y entonces.

Function arriba hay una interfaz funcional con solo un método apply. Toma un argumento y produce un resultado.


La razón por la cual :: son increíbles porque:

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.

es decir, como escribir un cuerpo lambda:

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

Simplemente puede hacer:

Function<Double, Double> square = Hey::square;

En tiempo de ejecución se comportan exactamente igual. El bytecode puede / no ser el mismo (para el caso anterior, genera el mismo bytecode (compilar arriba y verificar javap -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.

A continuación es ilegal:

Supplier<Boolean> p = Hey::square; // illegal

square espera un argumento y devuelve un doble. get método en Proveedor espera una discusión pero no devuelve nada. Entonces es un error

Método de referencia se refiere a un método de la interfaz funcional (Como se mencionó, la interfaz funcional solo puede tener un método).

Algunos ejemplos más: accept método en Consumidor 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

Encima getRandom no toma argumento y devuelve un doble. Entonces, cualquier interfaz funcional que satisfaga los criterios de: no tomes ningún argumento y regreses doble puede ser usado.

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;

Método de referencia se puede obtener en diferentes estilos, pero fundamentalmente todos significan lo mismo y simplemente se pueden visualizar como un lambda:

  1. Un método estático (ClassName::methName)
  2. Un método de instancia de un objeto particular (instanceRef::methName)
  3. Un súper método 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 mayor referencia: http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html


382
2018-03-07 08:47



Si eso es verdad. los :: el operador se usa para referencia de método. Entonces, uno puede extraer estático métodos de 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 del JDK 8 en esta artículo. En el Método / Referencia del constructor sección un ejemplo de código también se proporciona:

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);
   }
}

49
2017-11-15 12:51



:: es un nuevo operador incluido en Java 8 que se utiliza para referir 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 sintaxis es:

ClassName :: methodName 

Para referir métodos no estáticos, la sintaxis 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


22
2017-09-05 07:09



Esta es una referencia de método en Java 8. La documentación del oráculo es aquí.

Como se indica en la documentación ...

El método de referencia Persona :: compareByAge es una referencia a una estática   método.

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   eso es parte del objeto myComparisonProvider. El JRE infiere el   los argumentos del tipo de método, que en este caso son (Persona, Persona).


18
2017-11-15 12:52



Parece que es un poco tarde, pero aquí están mis dos centavos. UN 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 referencia de método nos permite hacer eso usando el operador de referencia de método :: .

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 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 algún 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 Comparador es una interfaz funcional. UN Interfaz funcional es el 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 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                       |
+------------------------------------------------------------+--------------------------------------+

9
2018-04-21 06:09



:: 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 solo UN 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 hacer 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;

4
2018-03-22 06:18