web-dev-qa-db-fra.com

Java 8 flux .min () et .max (): pourquoi cela compile-t-il?

Remarque: cette question provient d'un lien mort qui était une précédente question SO, mais voici ...

Voir ce code ( note: je sais que ce code ne "fonctionnera" pas et que Integer::compare devrait être utilisé - je viens de l'extraire de la question liée ):

final ArrayList <Integer> list 
    = IntStream.rangeClosed(1, 20).boxed().collect(Collectors.toList());

System.out.println(list.stream().max(Integer::max).get());
System.out.println(list.stream().min(Integer::min).get());

Selon le javadoc de .min() et .max() , l'argument des deux doit être un Comparator. Pourtant, ici, les références aux méthodes sont des méthodes statiques de la classe Integer .

Alors, pourquoi cela compile du tout?

207
fge

Laissez-moi vous expliquer ce qui se passe ici, car ce n'est pas évident!

Tout d’abord, Stream.max() accepte une instance de Comparator afin que les éléments du flux puissent être comparés les uns aux autres pour trouver le minimum ou le maximum, dans un ordre optimal que vous n’aurez pas. besoin de trop se soucier de.

La question est donc, bien sûr, pourquoi Integer::max accepté? Après tout, ce n'est pas un comparateur!

La réponse est dans la manière dont la nouvelle fonctionnalité lambda fonctionne dans Java 8. Elle repose sur un concept appelé de manière informelle interfaces "méthode abstraite unique" ou "interfaces SAM". L'idée est que toute interface avec une méthode abstraite peut être automatiquement implémentée par n'importe quelle méthode lambda - ou référence de méthode - dont la signature de méthode correspond à la méthode sur l'interface. Donc, en examinant l'interface Comparator (version simple):

_public Comparator<T> {
    T compare(T o1, T o2);
}
_

Si une méthode recherche un _Comparator<Integer>_, elle recherche essentiellement cette signature:

_int xxx(Integer o1, Integer o2);
_

J'utilise "xxx" car le nom de la méthode n'est pas utilisé à des fins d'appariement .

Par conséquent, Integer.min(int a, int b) et Integer.max(int a, int b) sont tous deux suffisamment proches pour que la sélection automatique permette que ceci apparaisse en tant que _Comparator<Integer>_ dans un contexte de méthode.

241
David M. Lloyd

Comparator est un interface fonctionnelle, et Integer::max est conforme à cette interface (après la prise en compte de la sélection automatique/du déballage). Il prend deux valeurs int et renvoie un int, exactement comme vous le souhaiteriez à un Comparator<Integer> to (encore une fois, plissant les yeux pour ignorer la différence Integer/int).

Cependant, je ne m'attendrais pas à ce qu'il fasse la bonne chose, étant donné que Integer.max ne respecte pas la sémantique de Comparator.compare. Et en effet, cela ne fonctionne pas vraiment en général. Par exemple, effectuez une petite modification:

for (int i = 1; i <= 20; i++)
    list.add(-i);

... et maintenant la valeur max est -20 et la valeur min est -1.

Au lieu de cela, les deux appels doivent utiliser Integer::compare:

System.out.println(list.stream().max(Integer::compare).get());
System.out.println(list.stream().min(Integer::compare).get());
115
Jon Skeet

Cela fonctionne car Integer::min résout une implémentation de l'interface Comparator<Integer>.

La référence à la méthode de Integer::min est résolue en Integer.min(int a, int b), résolue en IntBinaryOperator, et vraisemblablement une auto-substitution se produit quelque part, ce qui en fait un BinaryOperator<Integer>.

Et les méthodes min() resp max() de Stream<Integer> demandent à l'interface Comparator<Integer> d'être implémentée.
Ceci résout maintenant la méthode unique Integer compareTo(Integer o1, Integer o2). Qui est de type BinaryOperator<Integer>.

Et ainsi la magie est arrivée puisque les deux méthodes sont un BinaryOperator<Integer>.

19
skiwi

Outre les informations fournies par David M. Lloyd, on pourrait ajouter que le mécanisme qui permet cela s'appelle le typage à la cible .

L'idée est que le type que le compilateur assigne à une expression lambda ou à une référence de méthode ne dépend pas seulement de l'expression elle-même, mais également de l'endroit où elle est utilisée.

La cible d'une expression est la variable à laquelle son résultat est affecté ou le paramètre auquel son résultat est transmis.

Les expressions lambda et les références de méthodes se voient attribuer un type qui correspond au type de leur cible, si un tel type peut être trouvé.

Reportez-vous à la section Type Inference du Java Tutorial pour plus d'informations.

2
Lii

J'ai eu une erreur avec un tableau obtenant le maximum et le minimum donc ma solution était:

int max = Arrays.stream(arrayWithInts).max().getAsInt();
int min = Arrays.stream(arrayWithInts).min().getAsInt();
0
JPRLCol