: opérateur (double deux points) en Java 8


J'explorais la source Java 8 et j'ai trouvé cette partie particulière du code très surprenante:

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

Est Math::max quelque chose comme un pointeur de méthode? Comment une méthode normale static est-elle convertie en IntBinaryOperator?

Author: Olimpiu POP, 2013-11-15

14 answers

Habituellement, on appellerait la méthode reduce en utilisant Math.max(int, int) comme suit:

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

Cela nécessite beaucoup de syntaxe pour appeler simplement Math.max. C'est là que les expressions lambda entrent en jeu. Depuis Java 8, il est permis de faire la même chose de manière beaucoup plus courte:

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

, Comment cela fonctionne? Le compilateur java "détecte" que vous souhaitez implémenter une méthode qui accepte deux ints et en renvoie un int. Ceci est équivalent aux paramètres formels de la seule et unique méthode de interface IntBinaryOperator (le paramètre de la méthode reduce que vous voulez appeler). Donc, le compilateur fait le reste pour vous-il suppose simplement que vous voulez implémenter IntBinaryOperator.

Mais comme Math.max(int, int) remplit lui-même les exigences formelles de IntBinaryOperator, il peut être utilisé directement. Parce que Java 7 n'a pas de syntaxe permettant de passer une méthode elle-même en tant qu'argument (vous ne pouvez transmettre que des résultats de méthode, mais jamais de références de méthode), la syntaxe :: a été introduite dans Java 8 pour faire référence méthodes:

reduce(Math::max);

Notez que cela sera interprété par le compilateur, pas par la JVM au moment de l'exécution! Bien qu'il produise différents bytecodes pour les trois extraits de code, ils sont sémantiquement égaux, de sorte que les deux derniers peuvent être considérés comme des versions courtes (et probablement plus efficaces) de l'implémentation IntBinaryOperator ci-dessus!

(Voir aussi Traduction des expressions Lambda)

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

:: est appelé Référence de méthode. C'est essentiellement une référence à une seule méthode. I. e. il se réfère à une méthode existante par son nom.

Courte Explication:
Voici un exemple de référence à une méthode statique:

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 peut être transmis tout comme les références d'objet et déclenché en cas de besoin. En fait, il peut être aussi facilement utilisé comme référence à des méthodes" normales " d'objets que static. Par exemple:

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 ci-dessus est un interface fonctionnelle. Pour bien comprendre ::, il est important de comprendre également les interfaces fonctionnelles. Clairement, un interface fonctionnelle est une interface avec une seule méthode abstraite.

Exemples d'interfaces fonctionnelles comprennent Runnable, Callable, et ActionListener.

Function ci-dessus est une interface fonctionnelle avec une seule méthode: apply. Elle prend un argument et produit un résultat.


La raison pour laquelle les :: sont géniaux est que:

Les références de méthode sont des expressions qui ont le même traitement que les expressions lambda (...), mais au lieu de fournir un corps de méthode, ils font référence à une méthode existante par son nom.

Par exemple au lieu d'écrire le corps lambda

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

, Vous pouvez simplement faire

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

Lors de l'exécution, ces deux méthodes square se comportent exactement de la même manière. Le bytecode peut ou non être le même (bien que, pour le cas ci-dessus, le même bytecode soit généré; compiler ce qui précède et vérifier avec javap -c).

Le seul critère majeur à satisfaire est: la méthode que vous fournissez doit avoir une signature similaire à la méthode de l'interface fonctionnelle que vous utilisez comme référence d'objet.

Ce qui suit est illégal:

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

square attend un argument et retourne un double. La méthode getdans Supplier attend un argument mais ne renvoie rien. Ainsi, il en résulte une erreur.

Une référence de méthode fait référence à la méthode d'une interface fonctionnelle. (Comme mentionné, les interfaces fonctionnelles ne peuvent avoir qu'une seule méthode chacune).

Quelques autres exemples: la méthode acceptdans Consumer prend une entrée mais ne renvoie rien.

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

Ci-Dessus, getRandom ne prend aucun argument et retourne un double. Donc, toute interface fonctionnelle qui satisfait aux critères de: ne prenez aucun argument et retournez double peut être utilisé.

Un autre exemple:

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

Dans le cas de type paramétré:

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;

Les références de méthode peuvent avoir des styles différents, mais fondamentalement, elles signifient toutes la même chose et peuvent simplement être visualisées comme des lambdas:

  1. Une méthode statique (ClassName::methName)
  2. Une méthode d'instance d'un objet particulier (instanceRef::methName)
  3. Une super méthode d'un objet particulier (super::methName)
  4. Une méthode d'instance d'un objet arbitraire d'un type particulier (ClassName::methName)
  5. Une référence de constructeur de classe (ClassName::new)
  6. Une référence de constructeur de tableau (TypeName[]::new)

Pour plus de référence, voir http://cr.openjdk.java.net / ~briangoetz/lambda/lambda-state-final.html .

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

Oui, c'est vrai. L'opérateur :: est utilisé pour le référencement des méthodes. Ainsi, on peut extraire des méthodes static de classes en l'utilisant ou des méthodes d'objets. Le même opérateur peut être utilisé même pour les constructeurs. Tous les cas mentionnés ici sont illustrés dans l'exemple de code ci-dessous.

La documentation officielle d'Oracle peut être trouvée ici.

Vous pouvez avoir un meilleur aperçu des changements JDK 8 dans cet article. Dans la méthode/Constructeur référençant section un exemple de code est également fourni:

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

:: est un nouvel opérateur inclus dans Java 8 qui est utilisé pour se référer à une méthode d'une classe existante. Vous pouvez faire référence aux méthodes statiques et aux méthodes non statiques d'une classe.

Pour les méthodes statiques de référence, la syntaxe est:

ClassName :: methodName 

Pour les méthodes non statiques de référence, la syntaxe est

objRef :: methodName

Et

ClassName :: methodName

La seule condition préalable pour référencer une méthode est que la méthode existe dans une interface fonctionnelle, qui doit être compatible avec la référence de méthode.

Méthode les références, lorsqu'elles sont évaluées, créent une instance de l'interface fonctionnelle.

Trouvée sur: http://www.speakingcs.com/2014/08/method-references-in-java-8.html

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

Ceci est une référence de méthode dans Java 8. La documentation oracle est ici.

Comme indiqué dans la documentation...

La méthode de référence Person:: compareByAge est une référence à un méthode.

Voici un exemple de référence à une méthode d'instance objet particulier:

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 référence de méthode myComparisonProvider:: compareByName appelle la méthode compareByName c'est la partie de l'objet myComparisonProvider. Le JRE déduit le arguments de type méthode, qui dans ce cas sont (Personne, Personne).

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

Il semble que ce soit un peu tard mais voici mes deux cents. Une expression lambda est utilisée pour créer des méthodes anonymes. Il ne fait rien d'autre qu'appeler une méthode existante, mais il est plus clair de se référer à la méthode directement par son nom. Et la référence de méthode nous permet de le faire en utilisant l'opérateur de référence de méthode :: .

Considérez la classe simple suivante où chaque employé a un nom et un grade.

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

Supposons que nous ayons une liste d'employés renvoyés par certains méthode et nous voulons trier les employés par leur grade. Nous savons que nous pouvons utiliser classe anonyme comme:

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

Où getDummyEmployee () est une méthode comme:

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

Maintenant, nous savons que Le comparateur est une interface fonctionnelle. Une interface fonctionnelle est celle avec exactement une méthode abstraite (bien qu'elle puisse contenir une ou plusieurs méthodes par défaut ou statiques). Nous pouvons donc utiliser l'expression lambda comme:

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

Tout semble bon mais que se passe-t-il si le la classe Employee fournit également une méthode similaire:

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

Dans ce cas, l'utilisation du nom de la méthode lui-même sera plus claire. Par conséquent, nous pouvons directement nous référer à la méthode en utilisant la référence de la méthode comme:

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

Par docs, il existe quatre types de méthode références:

+----+-------------------------------------------------------+--------------------------------------+
|    | 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'opérateur a été introduit dans java 8 pour les références de méthodes. Une référence de méthode est la syntaxe abrégée d'une expression lambda qui exécute une seule méthode. Voici la syntaxe générale d'une référence de méthode:

Object :: methodName

, Nous savons que nous pouvons utiliser les expressions lambda au lieu d'utiliser une classe anonyme. Mais parfois, l'expression lambda n'est vraiment qu'un appel à une méthode, par exemple:

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

Pour rendre le code plus clair, vous pouvez tourner ce lambda expression dans une référence de méthode:

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

Le :: est connu sous le nom de références de méthode. Disons que nous voulons appeler une méthode calculatePrice d'achat de classe. Ensuite, nous pouvons l'écrire comme:

Purchase::calculatePrice

Il peut également être vu comme une forme courte d'écriture de l'expression lambda Car les références de méthode sont converties en expressions lambda.

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

À l'exécution, ils se comportent exactement de la même manière.Le bytecode peut/ne pas être le même (Pour les cas ci-dessus, il génère le même bytecode(conforme ci-dessus et vérifie javaap-c;))

À l'exécution, ils se comportent exactement de la même manière.méthode (math:: max);, il génère les mêmes mathématiques (conforme ci-dessus et vérifier javap-c;))

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

return reduce(Math::max); est PAS ÉGAL à à return reduce(max());

Mais cela signifie, quelque chose comme ceci:

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

, Vous pouvez simplement enregistrer 47 touches si vous écrivez comme ceci

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

En java-8 Streams Reducer in simple works est une fonction qui prend deux valeurs en entrée et renvoie le résultat après un calcul. ce résultat est alimenté dans l'itération suivante.

Dans le cas de la fonction Math:max, la méthode continue de renvoyer max de deux valeurs passées et à la fin vous avez le plus grand nombre en main.

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

Puisque de nombreuses réponses ici expliquaient bien :: le comportement, je voudrais également préciser que :: l'opérateur n'a pas besoin d'avoir exactement la même signature que l'interface fonctionnelle de référence s'il est utilisé par exemple variables. Supposons que nous ayons besoin d'un BinaryOperator qui a le type de TestObject . De manière traditionnelle, il est implémenté comme ceci:

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

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

            return t;
        }
    };

Comme vous le voyez dans l'implémentation anonyme, il nécessite deux arguments TestObject et renvoie un objet TestObject également. Pour satisfaire cette condition en utilisant l'opérateur ::, nous pouvons commencer par une méthode statique:

public class TestObject {


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

Puis appelez:

BinaryOperator<TestObject> binary = TestObject::testStatic;

Ok, il a bien compilé. Que dire si nous avons besoin d'une méthode d'instance? Permet de mettre à jour TestObject avec la méthode d'instance:

public class TestObject {

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

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

Maintenant, nous pouvons accéder exemple comme ci-dessous:

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

Ce code compile bien, mais en dessous d'un pas:

BinaryOperator<TestObject> binary = TestObject::testInstance;

My eclipse tell me " Ne peut pas faire de référence statique au non-statique méthode testInstance (TestObject, TestObject) à partir du type TestObject ..."

Assez juste, c'est une méthode d'instance, mais si nous surchargeons {[18] } comme ci-dessous:

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

Et appeler:

BinaryOperator<TestObject> binary = TestObject::testInstance;

Le code va juste bien compiler. Parce qu'il appellera testInstance avec un seul paramètre au lieu d'un double. Ok alors qu'est-ce qui s'est passé nos deux paramètres? Laisse imprimer et voir:

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

Qui sortira:

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

Ok donc JVM est assez intelligent pour appeler param1.testInstance (param2). Pouvons-nous utiliser testInstance à partir d'une autre ressource mais pas TestObject, c'est-à-dire:

public class TestUtil {

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

Et appeler:

BinaryOperator<TestObject> binary = TestUtil::testInstance;

Il ne compilera tout simplement pas et le compilateur dira: "Le type TestUtil ne définit pas testInstance(TestObject, TestObject)". Le compilateur recherchera donc une référence statique si ce n'est pas le même type. Ok ce que sur le polymorphisme? Si nous supprimons les modificateurs finaux et ajoutons notre classe SubTestObject :

public class SubTestObject extends TestObject {

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

}

Et appel:

BinaryOperator<TestObject> binary = SubTestObject::testInstance;

Il ne compilera pas aussi bien, le compilateur cherchera toujours une référence statique. Mais le code ci-dessous compilera bien car il passe le test is-a:

public class TestObject {

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

}

BinaryOperator<TestObject> binary = TestObject::testInstance;

* Je suis juste en train d'étudier donc j'ai compris en essayant de voir, n'hésitez pas à me corriger si je me trompe

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

J'ai trouvé cette source très intéressant.

En fait, c'est le Lambda, ce qui se transforme en un Double deux-points. Les deux points est plus lisible. Nous suivons ces étapes:

ÉTAPE 1:

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

ÉTAPE 2:

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

ÉTAPE 3:

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

Dans les anciennes versions de Java, au lieu de "::" ou lambd, vous pouvez utiliser:

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

Ou en passant à la méthode:

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