: (doppio colon) operatore in Java 8


Stavo esplorando la sorgente Java 8 e ho trovato questa particolare parte del codice molto 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;
}

Math::max è qualcosa come un puntatore del metodo? In che modo un normale metodo static viene convertito in IntBinaryOperator?

Author: Olimpiu POP, 2013-11-15

14 answers

Di solito, si chiamerebbe il metodo reduce usando Math.max(int, int) come segue:

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

Che richiede molta sintassi solo per chiamare Math.max. Ecco dove le espressioni lambda entrano in gioco. Da Java 8 è permesso fare la stessa cosa in un modo molto più breve:

reduce((int left, int right) -> Math.max(left, right));

Come funziona? Il compilatore java "rileva" che si desidera implementare un metodo che accetta due inte ne restituisce uno int. Questo è equivalente ai parametri formali dell'unico e unico metodo di interfaccia IntBinaryOperator (il parametro del metodo reduce che si desidera chiamare). Quindi il compilatore fa il resto per te - presuppone solo che tu voglia implementare IntBinaryOperator.

Ma poiché Math.max(int, int) soddisfa i requisiti formali di IntBinaryOperator, può essere utilizzato direttamente. Poiché Java 7 non ha alcuna sintassi che consenta di passare un metodo stesso come argomento (è possibile passare solo i risultati del metodo, ma mai i riferimenti al metodo), la sintassi :: è stata introdotta in Java 8 per fare riferimento metodi:

reduce(Math::max);

Si noti che questo verrà interpretato dal compilatore, non dalla JVM in fase di runtime! Sebbene produca bytecode diversi per tutti e tre i frammenti di codice, sono semanticamente uguali, quindi gli ultimi due possono essere considerati versioni brevi (e probabilmente più efficienti) dell'implementazione IntBinaryOperator sopra!

(Vedi anche Traduzione delle espressioni Lambda )

 852
Author: isnot2bad, 2018-06-04 20:42:32

:: si chiama Metodo di riferimento. È fondamentalmente un riferimento a un singolo metodo. Cioè si riferisce a un metodo esistente per nome.

Breve spiegazione:
Di seguito è riportato un esempio di riferimento a un metodo statico:

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 può essere passato in giro proprio come i riferimenti agli oggetti e attivato quando necessario. In effetti, può essere facilmente utilizzato come riferimento a metodi "normali" di oggetti come quelli static. Ad esempio:

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 sopra è un interfaccia funzionale . Per comprendere appieno ::, è importante comprendere anche le interfacce funzionali. Chiaramente, un'interfaccia funzionale è un'interfaccia con un solo metodo astratto.

Esempi di interfacce funzionali includono Runnable, Callable, e ActionListener.

Function sopra c'è un'interfaccia funzionale con un solo metodo: apply. Ci vuole un argomento e produce un risultato.


Il motivo per cui le :: sono fantastiche è che:

I riferimenti al metodo sono espressioni che hanno lo stesso trattamento delle espressioni lambda (...), ma invece di fornire un corpo di metodo, riferiscono un metodo esistente per nome.

Ad esempio, invece di scrivere il corpo lambda

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

Puoi semplicemente fare

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

In fase di esecuzione, questi due metodi square si comportano esattamente gli stessi l'uno dell'altro. Il bytecode può o non può essere lo stesso (anche se, per il caso precedente, lo stesso bytecode è generato; compilare quanto sopra e verificare con javap -c).

L'unico criterio importante da soddisfare è: il metodo fornito deve avere una firma simile al metodo dell'interfaccia funzionale utilizzata come riferimento all'oggetto.

Il seguente è illegale:

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

square si aspetta un argomento e restituisce un double. Il metodo get nel Fornitore prevede un argomento ma non restituisce nulla. Quindi, questo si traduce in un errore.

Un riferimento al metodo si riferisce al metodo di un'interfaccia funzionale. (Come accennato, le interfacce funzionali possono avere un solo metodo ciascuna).

Alcuni altri esempi: il metodo acceptin Consumer accetta un input ma non restituisce nulla.

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

Sopra, getRandom non accetta argomenti e restituisce un double. Quindi qualsiasi interfaccia funzionale che soddisfi i criteri di: non prendere argomenti e tornare double può essere usato.

Un altro esempio:

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

In caso di tipi parametrizzati:

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;

I riferimenti ai metodi possono avere stili diversi, ma fondamentalmente significano tutti la stessa cosa e possono essere semplicemente visualizzati come lambda:

  1. Un metodo statico(ClassName::methName)
  2. Un metodo di istanza di un particolare oggetto (instanceRef::methName)
  3. Un super metodo di un particolare oggetto (super::methName)
  4. Un metodo di istanza di un oggetto arbitrario di un particolare tipo (ClassName::methName)
  5. Un riferimento al costruttore di classe (ClassName::new)
  6. Un riferimento al costruttore di array (TypeName[]::new)

Per ulteriori riferimenti, vedere http://cr.openjdk.java.net / ~briangoetz/lambda/lambda-state-final.html .

 401
Author: Jatin, 2018-08-17 15:32:53

Sì, è vero. L'operatore :: viene utilizzato per il riferimento al metodo. Quindi, si possono estrarre i metodi statici dalle classi usandolo o metodi dagli oggetti. Lo stesso operatore può essere utilizzato anche per i costruttori. Tutti i casi menzionati qui sono esemplificati nell'esempio di codice qui sotto.

La documentazione ufficiale di Oracle può essere trovata qui.

Puoi avere una migliore panoramica delle modifiche JDK 8 in questo articolo. Nel metodo/Costruttore riferimento alla sezione viene fornito anche un esempio di codice:

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);
   }
}
 50
Author: Olimpiu POP, 2018-01-31 09:22:03

:: è un nuovo operatore incluso in Java 8 che viene utilizzato per fare riferimento a un metodo di una classe esistente. È possibile fare riferimento a metodi statici e metodi non statici di una classe.

Per i metodi statici di riferimento, la sintassi è:

ClassName :: methodName 

Per fare riferimento ai metodi non statici, la sintassi è

objRef :: methodName

E

ClassName :: methodName

L'unico prerequisito per fare riferimento a un metodo è che il metodo esiste in un'interfaccia funzionale, che deve essere compatibile con il riferimento al metodo.

Metodo

I riferimenti, quando valutati, creano un'istanza dell'interfaccia funzionale.

Trovato il: http://www.speakingcs.com/2014/08/method-references-in-java-8.html

 22
Author: sreenath, 2015-08-26 04:14:54

Questo è un riferimento al metodo in Java 8. La documentazione oracle è qui.

Come indicato nella documentazione...

La persona di riferimento del metodo:: compareByAge è un riferimento a una statica metodo.

Di seguito è riportato un esempio di riferimento a un metodo di istanza di oggetto particolare:

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

Il riferimento al metodo myComparisonProvider:: compareByName richiama il metodo compareByName questo fa parte dell'oggetto myComparisonProvider. Il JRE deduce il argomenti del tipo di metodo, che in questo caso sono (Persona, Persona).

 18
Author: david99world, 2015-09-26 12:02:24

Sembra che sia un po ' tardi, ma qui ci sono i miei due centesimi. Un'espressione lambda viene utilizzata per creare metodi anonimi. Non fa altro che chiamare un metodo esistente, ma è più chiaro fare riferimento al metodo direttamente dal suo nome. E il riferimento al metodo ci consente di farlo utilizzando l'operatore di riferimento al metodo ::.

Considera la seguente classe semplice in cui ogni dipendente ha un nome e un grado.

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

Supponiamo di avere una lista di dipendenti restituiti da alcuni metodo e vogliamo ordinare i dipendenti per il loro grado. Sappiamo che possiamo fare uso di classe anonima come:

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

Dove getDummyEmployee () è un metodo come:

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

Ora sappiamo che Comparator è un'interfaccia funzionale. Un'interfaccia funzionale è quella con esattamente un metodo astratto (sebbene possa contenere uno o più metodi predefiniti o statici). Quindi possiamo usare l'espressione lambda come:

employeeList.sort((e1,e2) -> e1.getGrade().compareTo(e2.getGrade())); // lambda exp

Sembra tutto buono ma cosa succede se il la classe Employee fornisce anche un metodo simile:

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

In questo caso l'utilizzo del nome del metodo stesso sarà più chiaro. Quindi possiamo fare riferimento direttamente al metodo usando il riferimento al metodo come:

employeeList.sort(Employee::compareByGrade); // method reference

Come per documenti ci sono quattro tipi di riferimenti al metodo:

+----+-------------------------------------------------------+--------------------------------------+
|    | 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                       |
+------------------------------------------------------------+--------------------------------------+
 12
Author: i_am_zero, 2018-07-03 06:07:23

:: L'operatore è stato introdotto in java 8 per i riferimenti ai metodi. Un riferimento al metodo è la sintassi abbreviata per un'espressione lambda che esegue un solo metodo. Ecco la sintassi generale di un riferimento al metodo:

Object :: methodName

Sappiamo che possiamo usare espressioni lambda invece di usare una classe anonima. Ma a volte, l'espressione lambda è in realtà solo una chiamata a qualche metodo, ad esempio:

Consumer<String> c = s -> System.out.println(s);

Per rendere il codice più chiaro, puoi trasformare quel lambda espressione in un riferimento al metodo:

Consumer<String> c = System.out::println;
 4
Author: Vaibhav9518, 2017-03-22 06:18:21

Il :: è noto come riferimenti al metodo. Diciamo che vogliamo chiamare un metodo calculatePrice di acquisto di classe. Quindi possiamo scriverlo come:

Purchase::calculatePrice

Può anche essere visto come una forma breve di scrittura dell'espressione lambda Perché i riferimenti al metodo vengono convertiti in espressioni lambda.

 3
Author: Sonu, 2016-11-15 18:45:00

In fase di esecuzione si comportano esattamente allo stesso modo.Il bytecode può / non essere lo stesso (Per sopra Incase, genera lo stesso bytecode (complie sopra e controlla javaap-c;))

In fase di esecuzione si comportano esattamente allo stesso modo.metodo(math::max);,genera la stessa matematica (complie sopra e controlla javap-c;))

 2
Author: Alfa khatoon, 2016-04-08 13:11:17

return reduce(Math::max); NON È UGUALE a a return reduce(max());

Ma significa, qualcosa del genere:

IntBinaryOperator myLambda = (a, b)->{(a >= b) ? a : b};//56 keystrokes I had to type -_-
return reduce(myLambda);

Puoi semplicemente salvare 47 sequenze di tasti se scrivi in questo modo

return reduce(Math::max);//Only 9 keystrokes ^_^
 2
Author: Jude Niroshan, 2016-07-23 05:57:09

In java-8 Streams Reducer in simple works è una funzione che prende due valori come input e restituisce il risultato dopo alcuni calcoli. questo risultato viene alimentato nella prossima iterazione.

In caso di Math:max function, method continua a restituire max di due valori passati e alla fine hai il numero più grande in mano.

 2
Author: Pramod, 2017-09-18 09:47:17

Poiché molte risposte qui hanno spiegato bene il comportamento ::, inoltre vorrei chiarire che :: l'operatore non ha bisogno di avere esattamente la stessa firma dell'interfaccia funzionale di riferimento se viene utilizzata ad esempio le variabili. Supponiamo di aver bisogno di un BinaryOperator che ha il tipo di TestObject. In modo tradizionale è implementato in questo modo:

BinaryOperator<TestObject> binary = new BinaryOperator<TestObject>() {

        @Override
        public TestObject apply(TestObject t, TestObject u) {

            return t;
        }
    };

Come si vede nell'implementazione anonima richiede due argomenti TestObject e restituisce anche un oggetto TestObject. Per soddisfare questa condizione usando l'operatore :: possiamo iniziare con un metodo statico:

public class TestObject {


    public static final TestObject testStatic(TestObject t, TestObject t2){
        return t;
    }
}

E poi chiamare:

BinaryOperator<TestObject> binary = TestObject::testStatic;

Ok ha compilato bene. Che dire se abbiamo bisogno di un metodo di istanza? Consente di aggiornare TestObject con il metodo di istanza:

public class TestObject {

    public final TestObject testInstance(TestObject t, TestObject t2){
        return t;
    }

    public static final TestObject testStatic(TestObject t, TestObject t2){
        return t;
    }
}

Ora possiamo accedere all'istanza come di seguito:

TestObject testObject = new TestObject();
BinaryOperator<TestObject> binary = testObject::testInstance;

Questo codice viene compilato bene, ma sotto uno no:

BinaryOperator<TestObject> binary = TestObject::testInstance;

Il mio eclipse mi dice " Non può fare un riferimento statico al non statico metodo testInstance (TestObject, TestObject) dal tipo TestObject ..."

Abbastanza giusto è un metodo di istanza, ma se sovraccarichiamo testInstance come di seguito:

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

E chiama:

BinaryOperator<TestObject> binary = TestObject::testInstance;

Il codice verrà compilato correttamente. Perché chiamerà testInstance con un singolo parametro invece di uno doppio. Ok quindi cosa è successo ai nostri due parametri? Consente di stampare e vedere:

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

Che produrrà:

 1418481495  
 303563356  
 Test instance called. this.hashCode:1418481495
 Given parameter hashCode:303563356

Ok quindi JVM è abbastanza intelligente da chiamare param1.testInstance (param2). Possiamo usare testInstance da un'altra risorsa ma non TestObject, cioè:

public class TestUtil {

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

E chiama:

BinaryOperator<TestObject> binary = TestUtil::testInstance;

Semplicemente non verrà compilato e il compilatore dirà: "Il tipo TestUtil non definisce testInstance(TestObject, TestObject)". Quindi il compilatore cercherà un riferimento statico se non è dello stesso tipo. Ok che dire del polimorfismo? Se rimuoviamo i modificatori finali e aggiungiamo la nostra classe SubTestObject:

public class SubTestObject extends TestObject {

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

}

E chiamata:

BinaryOperator<TestObject> binary = SubTestObject::testInstance;

Non verrà compilato, il compilatore cercherà comunque un riferimento statico. Ma sotto il codice verrà compilato bene poiché sta passando un test:

public class TestObject {

    public SubTestObject testInstance(Object t){
        return (SubTestObject) t;
    }

}

BinaryOperator<TestObject> binary = TestObject::testInstance;

* Sto solo studiando, quindi ho capito cercando di vedere, sentitevi liberi di correggermi se sbaglio

 1
Author: HRgiger, 2017-03-06 13:33:48

Ho trovato questa fonte molto interessante.

Infatti, è il Lambdache si trasforma in un Doppio Colon. Il doppio colon è più leggibile. Seguiamo questi passaggi:

FASE 1:

// We create a comparator of two persons
Comparator c = (Person p1, Person p2) -> p1.getAge().compareTo(p2.getAge());

FASE 2:

// We use the interference
Comparator c = (p1, p2) -> p1.getAge().compareTo(p2.getAge());

FASE 3:

// The magic
Comparator c = Comparator.comparing(Person::getAge());
 1
Author: Husam Bdr, 2017-08-26 05:25:20

Nelle versioni precedenti di Java, invece di "::" o lambd, è possibile utilizzare:

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 passando al metodo:

public static void doSomething(Action action) {
    action.execute();
}
 1
Author: Kamil Tomasz Jarmusik, 2018-02-04 15:14:48