En Java, pourquoi ne puis-je pas utiliser un lambda comme une expression améliorée pour la boucle?

Supposons que nous ayons un Iterator iterator . Iterable étant une interface fonctionnelle, nous pouvons écrire:

 Iterable iterable = () -> iterator; 

On peut alors bien sûr utiliser les iterable comme une expression améliorée pour la boucle:

 for (Integer i : iterable) foo(i); 

Alors pourquoi

 for (Integer i : () -> iterator) foo(i); 

interdit? (Il en résulte l’erreur de compilation suivante 🙂

 error: lambda expression not expected here for (Integer i : () -> iterator) foo(i); ^ 

Rendre le type de cible explicite comme

 for (Integer i : (Iterable) () -> iterator) foo(i); 

Cela fonctionne évidemment, mais pourquoi le compilateur ne peut-il pas déduire le type de cible de l’expression λ s’il est omis? Du fait que l’ expression est en notation λ, le compilateur ne devrait-il pas être clair que le type de cible ne peut pas être un Array et qu’il doit donc être Iterable ?

Est-ce simplement un oubli des concepteurs de langage ou y at-il autre chose qui me manque ici?

Il ne s’agit pas seulement d’expression lambda; il s’agit de toutes les expressions poly qui nécessitent un typage cible.

Une chose est sûre, ce n’est pas un oubli; l’affaire a été examinée et rejetée.

Pour citer une spécification précoce:

http://cr.openjdk.java.net/~dlsmith/jsr335-0.9.3/D.html

Décider des contextes autorisés à prendre en charge les poly expressions est principalement lié à la nécessité pratique de telles fonctionnalités:

L’expression dans une boucle améliorée n’est pas dans un contexte poly car, comme la construction est actuellement définie, c’est comme si l’expression était un récepteur: exp.iterator() (ou, dans le cas du tableau, exp[i] ) . Il est plausible qu’un iterator puisse être encapsulé dans une boucle for via une expression lambda ( for (Ssortingng s : () -> ssortingngIterator) ), mais cela ne cadre pas très bien avec la sémantique de Iterable.

Ma conclusion est que, chaque appel de Iterable.iterator() doit renvoyer un nouvel iterator indépendant, placé au début. Pourtant, l’expression lambda dans l’exemple (et dans votre exemple) renvoie le même iterator. Ceci n’est pas conforme à la sémantique d’ Iterable .


Dans tous les cas, il semble inutile de prendre en charge le ciblage de la cible dans chaque boucle. Si vous avez déjà l’iterator, vous pouvez simplement faire

  iterator.forEachRemaining( element->{ ... } ) 

Ou si vous préférez la vieille école

  while(iterator.hasNext()) { Foo elment = iterator.next(); 

Ni sont trop mauvais; cela ne vaut pas la peine de compliquer encore plus la spécification linguistique. (Si nous voulons que chaque type fournisse une dactylographie cible, rappelez-vous que cela doit fonctionner également pour d’autres expressions poly, comme ?: ; Alors, pour-chacune peut devenir trop difficile à comprendre dans certains cas. Et en général, il existe deux types de cibles possibles, Iterable | X[] , ce qui est très difficile pour le système d’inférence de type.)


La construction for-each pourrait être considérée comme un sucre syntaxique car lambda n’était pas disponible. Si le langage a déjà une expression lambda, il est vraiment inutile d’avoir une construction de langage spéciale à supporter pour-chacun; cela peut être fait par les API de la bibliothèque.

Dans la documentation de Lambda Expressions , ils répertorient les scénarios dans lesquels le type de cible peut être déduit, notamment:

Pour déterminer le type d’une expression lambda, le compilateur Java utilise le type de cible du contexte ou de la situation dans lequel l’expression lambda a été trouvée. Il s’ensuit que vous ne pouvez utiliser les expressions lambda que dans les cas où le compilateur Java peut déterminer un type de cible:

  • Déclarations de variables

  • Les devoirs

  • Déclarations de retour

  • Initialiseurs de tableaux

  • Arguments de méthode ou de constructeur

  • Corps d’expression lambda

  • Expressions conditionnelles,?:

  • Expressions de casting

Ce scénario n’est aucun de ceux-ci, le type de cible ne peut donc pas être déduit. Je conviens avec vous que le type de cible ne peut jamais être un tableau (car un tableau n’est pas une interface fonctionnelle), mais la documentation indique clairement qu’il ne s’agit pas de l’un de ces scénarios.

Spécifiquement, dans le JLS 15.27.3 , il est dit:

Une expression lambda est compatible dans un contexte d’affectation, un contexte d’invocation ou un contexte de conversion avec un type cible T si T est un type d’interface fonctionnelle (§9.8) et l’expression est conforme au type de fonction du type cible dérivé de T.

  • Contexte d’affectation – Les contextes d’affectation permettent d’affecter la valeur d’une expression (§15.26) à une variable.
  • Contexte d’invocation – Les contextes d’invocation autorisent une valeur d’argument dans une méthode ou un appel de constructeur
  • Contexte de casting – Les contextes de casting permettent à l’opérande d’un opérateur de casting (§15.16) d’être converti au type explicitement nommé par l’opérateur de casting.

Clairement, ce n’est rien de tout cela. Le seul qui soit même possible est un contexte d’invocation, mais la construction de boucle for améliorée n’est pas une invocation de méthode.


En ce qui concerne “pourquoi” ce scénario n’est pas autorisé par les auteurs Java, je n’en ai aucune idée. Parler aux esprits des écrivains de Java dépasse généralement le cadre de Stack Overflow; J’ai tenté d’expliquer pourquoi le code ne fonctionne pas, mais je ne peux pas deviner pourquoi ils ont choisi de l’écrire de cette façon.

Addendum, l’explication / discussion dans la réponse de @bayou.io est toujours présente dans la version finale de JSR-335 :

Les expressions lambda et les références de méthodes ne peuvent apparaître que dans certains contextes et leur type et leur exactitude sont déterminés par ce contexte. D’autres types d’expressions dans le langage existant ont déjà introduit des dépendances sur le contexte, et cette tendance semble devoir se poursuivre. Plutôt que de traiter chaque nouvelle fonctionnalité de manière ad hoc, l’introduction de la poly expression et la reconnaissance explicite du fait que les types de cible peuvent influencer les types d’expression nous permettent d’unifier le traitement des expressions dépendantes du contexte sous un même parapluie.

… snip

L’expression dans une boucle améliorée n’est pas dans un contexte poly car, comme la construction est actuellement définie, c’est comme si l’expression était un récepteur: exp.iterator() (ou, dans le cas du tableau, exp[i] ) . Il est plausible qu’un iterator puisse être encapsulé dans une boucle for via une expression lambda ( for (Ssortingng s : () -> ssortingngIterator) ), mais cela ne cadre pas très bien avec la sémantique de Iterable.

Comme durron597 vous explique que vous ne pouvez utiliser une expression lambda que dans les cas où le compilateur Java peut déterminer un type de cible.

Dans une boucle pour chaque:

for (FormalParameter: Expression) Statement

Le JLS dit que:

Le type de l’expression doit être Iterable ou un type de tableau (§10.1), ou une erreur de compilation se produit.

Ce qui signifie que deux types différents sont autorisés, et non l’un des requirejs pour une expression lambda. Il était donc impossible de concevoir le langage de manière à ce que les expressions lambda fonctionnent dans des boucles pour chaque boucle. Pas avec le concept d’expression lambda utilisé.

Lorsque vous ajoutez une dissortingbution, un seul type est autorisé. Le compilateur peut donc déterminer un type de cible pour l’expression lambda. C’est pourquoi “Rendre le type de cible explicite” fonctionne.