: opérateur (double colon) en Java 8

j'explorais la source Java 8 et j'ai trouvé cette partie 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-ce que Math::max ressemble à un pointeur de méthode? Comment une méthode normale static peut-elle être convertie en IntBinaryOperator ?

776
demandé sur Olimpiu POP 2013-11-15 16:46:32

14 réponses

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

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

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

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

mais comme Math.max(int, int) lui-même répond aux exigences de forme de IntBinaryOperator , il peut être utilisé directement. Parce que Java 7 n'a pas de syntaxe qui permet à une méthode même d'être passé comme argument (vous can only pass method results, but never method references), la syntaxe :: a été introduite en Java 8 pour les méthodes de référence:

reduce(Math::max);

notez que cela sera interprété par le compilateur, pas par la JVM à l'exécution! Bien qu'il produise des bytecodes différents 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
répondu isnot2bad 2018-06-04 20:42:32

:: S'appelle méthode de référence. Il s'agit d'une référence à une méthode unique. I. e. il se réfère à une méthode existante par son nom.

Brève 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 comme des références d'objet et déclenché si nécessaire. En fait, il peut être facilement utilisé comme référence aux méthodes " normales "des objets comme static ceux. 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 une interface fonctionnelle . Pour bien comprendre :: , il est également important de comprendre les interfaces fonctionnelles. Manifestement, une interface fonctionnelle est une interface avec une seule méthode abstraite.

exemples d'interfaces fonctionnelles: 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 :: s sont impressionnant 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 référez une méthode existante par son nom.

E. G. 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;

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

le seul le principal critère à satisfaire est: la méthode que vous fournissez devrait 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 renvoie un double . La méthode get dans Supplier attend un argument mais ne renvoie rien. Ainsi, cette provoque une erreur.

une référence de méthode se réfère à la méthode d'une interface fonctionnelle. (comme mentionné, les interfaces fonctionnelles ne peuvent avoir qu'une méthode chacune).

quelques autres exemples: la méthode accept dans consommateur prend un input 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 renvoie un double . De sorte que toute interface fonctionnelle qui répond aux critères de: prendre aucun argument et retourner double peut être utilisé.

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 types paramétrés :

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 ils veulent tous la même chose et peuvent simplement être visualisés comme 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 de l'arbitraire d'un objet d'un type particulier ( ClassName::methName )
  5. Un constructeur de la classe de référence ( ClassName::new )
  6. un constructeur de tableaux référence ( TypeName[]::new )

Pour de plus amples informations, voir http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html .

401
répondu Jatin 2018-08-17 15:32:53

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

la documentation officielle D'Oracle peut être trouvée ici .

vous pouvez avoir un meilleur aperçu de la JDK 8 les changements de ce de l'article. Dans la section méthode / constructeur faisant référence à , 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
répondu Olimpiu POP 2018-01-31 09:22:03

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

pour référencer les méthodes statiques, la syntaxe est:

ClassName :: methodName 

Pour renvoyer des méthodes non statiques, la syntaxe est

objRef :: methodName

et

ClassName :: methodName

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

Les références de méthode

, 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
répondu sreenath 2015-08-26 04:14:54

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

comme indiqué dans la documentation...

la personne de référence de la méthode::compareByAge est une référence à un méthode.

ce qui suit est un exemple d'une référence à une méthode d'instance d'une 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); 

le référence de la méthode miscomparisonprovider:: compareByName invoque la méthode compareByName Cela fait partie de l'objet miscomparisonprovider. La JRE conclut que arguments de type de méthode, qui dans ce cas sont (Personne, personne).

18
répondu david99world 2015-09-26 12:02:24

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

considérer la classe simple suivante où chaque employé a un nom et une classe.

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 retournés par une méthode quelconque et que nous voulions trier les employés selon leur grade. Nous savons que nous pouvons faire usage de classe anonyme :

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

il semble tout bon, mais que faire si 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 elle-même sera plus claire. Nous pouvons donc nous référer directement à la méthode en utilisant la référence de la méthode comme:

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

selon docs il y a quatre types de références de méthode:

+----+-------------------------------------------------------+--------------------------------------+
|    | 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
répondu i_am_zero 2018-07-03 06:07:23

:: L'opérateur a été introduit en java 8 pour les références de méthode. Une référence de méthode est la syntaxe raccourcie pour une expression lambda qui n'exécute qu'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 des les expressions lambda au lieu d'utiliser une classe anonyme. Mais parfois, l'expression lambda n'est qu'un appel à une méthode, par exemple:

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

pour rendre le code plus clair, vous pouvez transformer cette expression lambda en référence de méthode:

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

Les":: "est connu comme la méthode de référence. Disons que nous voulons appeler une méthode de calculatrice D'achat de classe. Alors nous pouvons l'écrire comme:

Purchase::calculatePrice

il peut également être considéré comme une forme courte d'écriture de l'expression lambda parce que les références de méthode sont converties en expressions lambda.

3
répondu Sonu 2016-11-15 18:45:00

à l'exécution, ils se comportent exactement de la même façon.Le bytecode peut/ne pas être le même (pour Incase ci-dessus,il génère le même bytecode (complie ci-dessus et cochez javaap-c;))

à l'exécution, ils se comportent exactement de la même façon.méthode(math::max);,il génère le même pour les mathématiques (complie ci-dessus et vérifier javap -c;))

2
répondu Alfa khatoon 2016-04-08 13:11:17

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

mais ça veut dire, quelque chose comme ça:

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

Vous pouvez tout simplement enregistrer 47 touches si vous écrivez comme ça

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

en Java-8 Streams Reducer dans des travaux simples est une fonction qui prend deux valeurs en entrée et retourne le résultat après un certain calcul. ce résultat est introduit dans la prochaine itération.

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

2
répondu Pramod 2017-09-18 09:47:17

étant donné que de nombreuses réponses expliquaient bien le comportement :: , je voudrais en outre 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 si elle est utilisée par exemple les variables . Supposons que nous ayons besoin d'un BinaryOperator qui a le type de TestObject . De manière traditionnelle son mis en œuvre 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. 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 compilé fine. 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:

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

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

BinaryOperator<TestObject> binary = TestObject::testInstance;

Mon eclipse me dire "Ne peut pas faire une référence statique pour les non méthode statique testInstance(TestObject, TestObject) à partir du type TestObject ..."

juste assez son une méthode d'instance, mais si nous surchargeons testInstance 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 appelez:

BinaryOperator<TestObject> binary = TestObject::testInstance;

le code compilera très bien. Parce qu'il appellera testInstance avec un seul paramètre au lieu d'un double. Que s'est-il passé avec nos deux paramètres? Imprimons et voyons:

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

 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 d'une autre ressource mais pas TestObject, i.e.:

public class TestUtil {

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

Et appelez:

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) " . Donc le compilateur va chercher une référence statique si elle n'est pas du même type. Ok ce que sur le polymorphisme? Si nous supprimons les modificateurs finaux et ajoutons notre SubTestObject classe:

public class SubTestObject extends TestObject {

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

}

et appelez:

BinaryOperator<TestObject> binary = SubTestObject::testInstance;

Il ne compilera pas aussi bien, le compilateur cherchera quand même une référence statique. Mais ci-dessous le code compilera bien puisqu'il réussit est-un test:

public class TestObject {

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

}

BinaryOperator<TestObject> binary = TestObject::testInstance;

*je suis juste étudiant donc j'ai pu en essayer et voir, n'hésitez pas à me corriger si je me trompe

1
répondu HRgiger 2017-03-06 13:33:48

j'ai trouvé cette source très intéressante.

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

etape n ° 1:

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

etape n ° 2:

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

ETAPE 3:

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

dans les anciennes versions 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 passage à la méthode:

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