: 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
?
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 )
::
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:
- Une méthode statique (
ClassName::methName
) - Une méthode d'instance d'un objet particulier (
instanceRef::methName
) - Une super méthode d'un objet particulier (
super::methName
) - Une méthode d'instance de l'arbitraire d'un objet d'un type particulier (
ClassName::methName
) - Un constructeur de la classe de référence (
ClassName::new
) - 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 .
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);
}
}
::
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
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).
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 |
+------------------------------------------------------------+--------------------------------------+
:: 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;
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.
à 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;))
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 ^_^
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.
é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
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());
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();
}