217 votes

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

Note : cette question provient d'un lien mort qui était une question précédente de l'OS, mais c'est parti...

Voir ce code ( note : je sais que ce code ne "fonctionnera" pas et que Integer::compare devrait être utilisée -- je l'ai juste extraite 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 la javadoc de .min() y .max() l'argument des deux devrait être un Comparator . Pourtant, ici, les références aux méthodes sont des méthodes statiques de l'élément Integer classe.

Alors, pourquoi est-ce que ça compile ?

6 votes

Notez que cela ne fonctionne pas correctement, il faudrait utiliser Integer::compare au lieu de Integer::max y Integer::min .

0 votes

@ChristofferHammarström Je le sais ; notez comment j'ai dit avant l'extrait de code "Je sais, c'est absurde".

3 votes

Je n'essayais pas de te corriger, je disais aux gens en général. Vous avez donné l'impression que vous pensiez que la partie absurde est que les méthodes de Integer ne sont pas des méthodes de Comparator .

243voto

David M. Lloyd Points 1145

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

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 dont vous n'avez pas à vous soucier outre mesure.

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

La réponse se trouve dans la manière dont la nouvelle fonctionnalité lambda fonctionne dans Java 8. Elle s'appuie sur un concept connu de manière informelle sous le nom d'interfaces "méthode abstraite unique", ou interfaces "SAM". L'idée est que toute interface comportant une méthode abstraite peut être automatiquement mise en œuvre par tout lambda - ou référence de méthode - dont la signature de méthode correspond à la méthode de l'interface. Ainsi, en examinant l'interface Comparator (version simple) :

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

Si une méthode cherche un Comparator<Integer> alors il cherche 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 de correspondance .

Par conséquent, les deux Integer.min(int a, int b) y Integer.max(int a, int b) sont suffisamment proches pour que l'autocommutateur permette de les faire apparaître comme une Comparator<Integer> dans un contexte de méthode.

28 votes

Ou alternativement : list.stream().mapToInt(i -> i).max().get() .

14 votes

@assylias Vous voulez utiliser .getAsInt() au lieu de get() cependant, comme vous avez affaire à un OptionalInt .

0 votes

... alors que ce que nous essayons de faire, c'est de fournir un comparateur personnalisé à une max() fonction !

118voto

Jon Skeet Points 692016

Comparator es un interface fonctionnelle y Integer::max est conforme à cette interface (après prise en compte de l'autoboxing/unboxing). Il faut deux int et renvoie un int - tout comme vous vous attendez à ce qu'un Comparator<Integer> (à nouveau, en 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 n'est pas conforme à la sémantique de Comparator.compare . Et en effet, cela ne fonctionne pas vraiment en général. Par exemple, faites un petit changement :

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

... et maintenant le max est de -20 et la valeur min est de -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());

1 votes

Je connais les interfaces fonctionnelles et je sais pourquoi elles donnent de mauvais résultats. Je me demande juste comment diable le compilateur ne m'a pas crié dessus

0 votes

@fge Parce que c'est techniquement correct, mais je me demande s'il y a un moyen d'éviter cela ?

6 votes

@fge : C'est le déballage qui n'était pas clair pour vous ? (Je n'ai pas regardé exactement cette partie.) A Comparator<Integer> aurait int compare(Integer, Integer) ... il n'est pas ahurissant que Java permette une référence de méthode de int max(int, int) à convertir en cela...

19voto

skiwi Points 8321

Cela fonctionne parce que Integer::min se résume à une mise en œuvre de la Comparator<Integer> interface.

La méthode de référence de Integer::min se résout à Integer.min(int a, int b) Il a été décidé de IntBinaryOperator et l'on peut supposer qu'il y a une boîte automatique quelque part. BinaryOperator<Integer> .

Et le min() resp. max() les méthodes de l Stream<Integer> demander au Comparator<Integer> à mettre en œuvre.
Maintenant, cela se résume à la méthode unique Integer compareTo(Integer o1, Integer o2) . Qui est de type BinaryOperator<Integer> .

Et c'est ainsi que la magie opère puisque les deux méthodes sont une BinaryOperator<Integer> .

0 votes

Il n'est pas tout à fait correct de dire que Integer::min met en œuvre Comparable . Ce n'est pas un type qui peut implémenter quoi que ce soit. Mais il est évalué en un objet qui implémente Comparable .

1 votes

@Lii Merci, je viens de le corriger.

1 votes

Comparator<Integer> est une interface à une seule méthode abstraite (aussi appelée "fonctionnelle"), et Integer::min remplit son contrat, donc le lambda peut être interprété comme ceci. Je ne sais pas comment vous voyez BinaryOperator entrer en jeu ici (ou IntBinaryOperator, non plus) - il n'y a pas de relation de sous-typage entre cela et Comparator.

2voto

Lii Points 690

Outre les informations données par David M. Lloyd, on pourrait ajouter que le mécanisme qui permet cela est appelé typage de la cible .

L'idée est que le type que le compilateur attribue à une expression lambda ou à une référence de méthode ne dépend pas seulement de l'expression elle-même, mais aussi 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 passé.

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

Voir le Section inférence de type dans le tutoriel Java pour plus d'informations.

1voto

JPRLCol Points 512

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

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X