Mardi 13 mars 2012, Rémi Forax est venu au Paris JUG (Java User Group). Le Paris JUG est une association crée en 2008 par Antonio Goncalvès, qui organise chaque mois une soirée sur Java. Avec plus de 220 personnes à chaque soirée, des inscriptions qui sont sold-out quelques jours avant, le Paris JUG est un groupe très dynamique.
Quelques mots sur l’enseignement
Rémi Forax est enseignant-chercheur, Maître de conférence à l’université Paris Est – Marne-la-vallée. Il débute sa présentation par quelques mots sur les étudiants. Les Licences de 3ème année, les Master 1 et 2, effectuent une partie de leur cursus en stage, ou sous la forme de l’alternance avec un poste dans une entreprise. 40% des étudiants font de l’alternance. L’université propose un cursus universitaire Bac+8, pour aller vers la recherche, et un cursus ingénieur classique, avec un niveau Bac+5. L’apprentissage est un excellent moyen pour les entreprises de former de futurs collaborateurs, et de leur proposer l’expérience de l’entreprise. Côté entreprise, l’exemption de charges sociales, le coût limité et la possibilité de former un étudiant sont de gros avantages. En mai, venez participer aux Rencontres de l’apprentissage pour découvrir différentes formations.
Quelques mots échangés avec Rémi avant la séance sur les étudiants, confirment ce que j’entends par ailleurs. Le nombre d’étudiant reste stable, mais la part des femmes dans les effectifs reste très bas. Beaucoup d’étudiants sélectionnent l’informatique, en pensant que les mathématiques ne seront pas aussi importants. Or dans la démarche d’apprentissage et de formalisation, avoir un bon bagage de base est important. Les concepts simples d’itération, de boucles, de structure de codes ne sont pas connus des débutants. Il serait vraiment important d’enseigner les bases de la programmation dès le lycée. Globalement, les étudiants trouvent sans problèmes du travail en sortant de la filière informatique. L’Université de Paris-Est Marne-la-Vallée a une excellente réputation. Je connais d’ailleurs 3 personnes passées par cette formation et par Ingénieur 2000, et ce sont d’excellents développeurs.
Au coeur des expressions Lambda en Java
La présentation de ce soir porte sur les expressions Lambda. Un sujet passionnant, une fonctionnalité que C# a déjà depuis quelques temps, mais que Java n’a pas encore.
Rémi est membre de l’expert group du JCP, sur la JSR 335.Il travaille avec Brian Goetz ou Doug Lea, des noms qui doivent vous dire quelque chose. La JSR-335 faite partie de la JSR-337 qui vise à formaliser le contenu de la future version 8 de Java. Pour rappel, cette JSR-337 avait été annoncée à Devoxx 2010 par les équipes d’Oracle. Si tout se passe comme prévu, nous aurons tout cela dans Java 8 en 2013, ou 2014.
Rémi explique qu’il y a 2 moyens d’ajouter des choses dans le JDK. Soit de petites modifications, qui sont livrées dans les différents « update » du JDK, soit en écrivant une API et en se débrouillant pour qu’elle devienne un standard. C’est le cas de l’API des collections, écrites par Doug Lea.
Si l’on regarde les versions de Java, comme le dit Hugo Lassiège sur son blog, depuis quelques années il ne se passait pas grand chose dans notre monde. Mais depuis fin 2010, et que le rachat de SUN par Oracle a été acté, les choses repartent.
Petit rappel :
- java 1.0 : 1996
- java 1.1 : 1997
- java 1.2 : 1998
- java 1.3 : 2000
- java 1.4 : 2002
- java 1.5 : 2004
- java 1.6 : 2006
- java 1.7 : sorti en juillet 2011
- java 1.8 : annoncé pour 2012
- java 1.9 : 2013 ?
Rémi explique que depuis quelques années, la politique globale sur la JVM est : on ne touche pas à la JVM. Cela entraîne des choix délibérés comme la non-réification des Generics. En clair, les paramètres typés ne sont pas conservés à l’exécution. Les generics en Java sont implémentés en utilisant l’erasure. En bref, les types génériques sont retirés à l’exécution. Ils gardent donc un intérêt à la compilation, en vous signalant les erreurs de type, et en vous évitant d’ajouter des casts dans votre code à tout bout de champ. Relisez un vieux numéro du Touilleur Express de 2009, où Alexandre Bertails, qui sera aussi à Devoxx France 2012, vous explique tout cela.
// le byte-code généré est identique, le type <Integer> n'est pas conservé. public void compute(List<Integer> listOfIntegers) ; public void compute(List listOfIntegers) ;
Many people are unsatisfied with the restrictions caused by the way generics are implemented in Java. Specifically, they are unhappy that generic type parameters are not reified: they are not available at runtime. Generics are implemented using erasure, in which generic type parameters are simply removed at runtime. That doesn’t render generics useless, because you get typechecking at compile-time based on the generic type parameters, and also because the compiler inserts casts in the code (so that you don’t have to) based on the type parameters.
La JVM est l’un des systèmes les plus puissants et les mieux réussis en Java. Plutôt que de se concentrer sur le langage, Rémi explique que l’important, c’est de comprendre la JVM, pour ensuite seulement, travailler sur le langage. Ce qui a changé les choses, c’est le passage vers l’open-source de la JVM. Cela a aidé les chercheurs à comprendre le fonctionnement de celle-ci. Et à en proposer des évolutions.
invokeDynamis (JSR-292) est né, Rémi Forax est aussi l’un des membres de cet expert-group, soit-dit en passant. Ce nouveau mot clé dans la JVM permet à un langage de définir une méthode, puis de changer à l’exécution le pointeur vers la méthode à exécuter réellement. Oui, appelez cela des pointeurs de fonction si vous voulez. invokeDynamics est donc bien une nouvelle instruction dans la JVM, qui permet de supporter plus facilement les langages orientés objets typés dynamiquement comme Groovy. Mais Rémi explique que c’est aussi le moyen d’améliorer Java lui-même. En quelques sortes, j’ai l’impression que les gens n’ont pas compris que la JSR-292, c’est d’abord pour Java. Et ça, ça va faire mal à d’autres langages alternatifs sur la JVM.
Rémi Forax discute ensuite un peu sur les langages dynamiques. L’une des possibilités avec un langage dynamique comme Ruby ou Groovy, c’est de séparer par exemple le code qui itère une fonction, du code utilisé lors de l’itération. Si Groovy supporte les Closures, c’est tout simplement car c’est facile avec les langages dynamiques. Cela a un coût en terme de performances, d’où la JSR-292, mais c’est une des fonctions les plus pratiques, que nous n’avons toujours pas en Java.
Aujourd’hui, les langages fortement typés comme Scala ont plus le vent en poupe. Du côté des performances, ils sont plus efficaces. Ruby par son côté dynamique, facilite l’écriture des choses. Mais des tests simples sur des algorithmes connus montrent qu’il n’est pas plus puissant. A tel point, que JRuby était plus efficace que Ruby. Je ne sais pas si cette information est toujours vraie.
Alors il y a Java.
Et le langage est un peu à la ramasse. Lorsque l’on regarde ce que l’on peut écrire en 3 lignes de Ruby, ou les manipulations des fonctions en C# avec les lambda, on ne peut s’empêcher d’avoir un petit sourire poli pour les Java-istes. Toujours persuadé d’être les rois du pétrole avec nos 455 frameworks, nous avons laissé de côté les discussions sur le langage…
Heureusement pour nous, les choses changent.
Je vais te dire mon sentiment cher lecteur. Je pense que Java 8 va mettre en difficulté l’intérêt de quelques langages alternatifs comme Scala, Kotlin ou Ceylon. Si Java 8 sort avec les lambdas, alors je pense que la communauté aura moins d’intérêt à étudier et apprendre ces nouveaux langages. Lorsque l’on voit l’attentisme face à Scala, le manque d’ouverture de la majorité des Java-istes (surtout les vieux), Java 8 arrivera comme une purée pour les papys qui n’ont plus les dents pour croquer Scala. Mais pour l’instant, c’est pas gagné. Continuez à regarder les autres langages, on vous dira quand Java sera prêt.
Revenons à la présentation…
Inner-class anonymes et Lambda
Comme vous le savez, en Java il est possible d’écrire des inner-classes anonymes. Rémi explique que lorsque l’on écrit un Runnable, peut-être l’un des premiers programmes que vous avez écrit en Java avec des Threads, vous écrivez basiquement un lambda, qui sera exécuté par une Thread. La déclaration et l’exécution sont séparés. Cette séparation permet de retirer du code technique, pour ne garder que le code métier. Regardez votre sourire lorsque vous écrivez un Comparable ou un Runnable : vous n’avez que l’essentiel, pas toute la partie technique à gérer.
Ca y est, vous venez de comprendre le principe d’une lambda expression.
En Java, nous aimerions donc pouvoir créer des pointeurs de fonctions, ou des fonctions anonymes. Rémi fait ensuite une digression, en parlant de Scala. Intéressant, il donne un retour qui m’a semblé assez négatif sur le langage. Lorsqu’il l’enseigne, il constate que les étudiants sont incapables de relire du code écrit par d’autres étudiants. Il a eu lui même quelques soucis à débuger, et il est assez circonspect sur le langage. Cela mériterait bien une interview, surtout lorsque l’on regarde le parcours de Rémi, je pense que son avis a une valeur universitaire intéressante.
Ce qui est visé avec le travail sur les Lambdas pour Java, va révolutionner la conception et la programmation dans notre langage de vieux. Cela va ouvrir les portes à de la simplification et va redonner de nombreuses heures de travail aux créateurs de framework comme Spring ou Hibernate.
Les gars, tenez-bon : on parle moins de vous, mais avec Java 8 vous allez remanger.
Sur le papier, Java a des lambdas depuis la version 1.1 avec l’introduction des innerClasses.
Java a des lambdas depuis la version 1.1 avec les innerClasses. Rémi Forax a demandé à John Rose, celui qui a codé le système d’inner-class dans la JVM, pourquoi il n’avait pas mis de lambda en Java. Et la réponse de l’intéressé : parce que les inner-classes sont plus puissantes.
Bon j’imagine que la discussion devait être plus compliquée, mais bon…
Cependant sur les inner-classes, à part la façon d’écrire le code qui n’est pas très élégante, cela coûte un objet à chaque fois. Guava est une très belle librairie, mais elle n’est pas écologique pour un sous. Tout ce qui est Prediate et compagnie, cela consomme de la mémoire les amis. L’intérêt des lambdas va vous sauter aux yeux dans quelques minutes. Mais retenez un mot : performance. Voilà.
Brian Goetz liste les imperfections des inner-class anonymes :
- Bulky syntax
- Confusion surrounding the meaning of names and
this
- Inflexible class-loading and instance-creation semantics
- Inability to capture non-final local variables
- Inability to abstract over control flow
Dessine moi une Lambda expression
Les lambdas simplifient l’écriture du code. Et surtout sa lecture. L’idée est que le développeur devrait se concentrer sur la partie signifiante, pas sur la tuyauterie technique. Une lambda s’écrit simplement, la syntaxe retenue n’est pas celle de Groovy, mais celle de C#/Scala. Les paramètres sont inférés, le compilateur le fait sans soucis dans d’autres langages. Un point important : les lambdas ne sont pas des inner-class. Nous allons voir comment la JVM et l’introduction du concept de default method permet d’ajouter le support des Lambdas, sans mettre le feux à votre belle application qui tourne sur Websphere.
(int x, int y) -> x + y
() -> 42
(String s) -> { System.out.println(s); }
Ci-dessus, 3 exemples de lambda. La première, prend 2 integers et retourne la somme. La deuxième ne prend pas d’arguments et retourne 42. Enfin la troisième prend une chaine, et ne retourne rien, puisque le retour de la méthode println de PrintStream est void.
La syntaxe est donc très simple : des arguments à gauche, forcément typés, la flèche -> puis soit une expression, soit un bloc de code. Lorsqu’un bloc de code est évalué, le résultat de la dernière expression est le type de retour de la lambda. C’est le même principe avec d’autres langages comme Scala. Il n’y a donc pas de « return ».
Est-ce que les lambdas sont typés ? Disons que le type d’une Lambda est déterminé par le compilateur, de manière intelligente. Pour cela, il le fait en examinant les paramètres de la lambda, à gauche de la flèche. Rémi explique que c’est un type structuré, pas un type nominal comme pour une expression. Les lambdas ne sont pas des objets. Le type n’est pas inféré selon le nom de la lambda, mais bien selon le contexte. Pour en savoir plus, lisez l’article de Brian Goetz que je trouve passionnant et très bien écrit.
Sur la syntaxe des lambdas, des discussions enflammées en 2007 ont un peu animé les choses. Entre temps, tout le monde a aussi compris qu’il était nécessaire de trouver une solution pour conserver une compatibilité avec les librairies actuelles. Lorsque vous définissez une interface avec une seule méthode, vous créez ce que l’on appele des SAM, ou des interfaces fonctionnelles. Prennez Runnable, Comparator ou ActionListener : ceci permet déjà d’introduire les lambdas facilement.
FileFilter filter = file -> file.isDirectory(); Callablecallable = () -> { return fib(17); }; Predicatepredicate = p -> p.lastName != null;
Un point important concerne la porté, la capture des variables. Vous savez déjà que lorsqu’une inner-class anonyme reçoit des paramètres, ceux-ci sont déclarés comme final. Sur ce point, le fonctionnement des Lambdas fait que par défaut, lorsque le paramètre n’est pas modifié, il est implicitement final.
L’expresion suivante par exemple est simple : X et Y sont finaux, ou implicitement finaux.
(int x, int y) -> x+ y ;
Du coup, n’imaginez pas pouvoir avoir de variables mutables, et donc la possibilité d’écrire ce type de code :
int sum = 0; list.forEach(e -> { sum += e.size(); }); // FAUX, Pas Possible, stupid idea, RTFM
Permettre ce code empêchera de pouvoir exécuter le code de la lambda expression de manière isolée, sur plusieurs processeurs par exemple.
Alors comment je fais la somme de la taille de mes éléments moi ?
Et bien avec simplement l’approche Map/Reduce. Pour rappel, si vous n’avez pas eu de cours de lambda programmation, une fonction de Map c’est « pour un élément donné, je veux l’autre ». Pour Reduce : pour tous les éléments, je veux un autre résultat. Et enfin filter : j’en veux une partie.
int sum = list.map(e -> e.size()).reduce(0, (a, b) -> a+b);
Pour chaque élément de la liste, je veux la taille. J’applique ensuite une réduction, qui est ici la somme des tailles de chaque élément de la liste.
Les Methods References
Un nouveau principe qui va faire fureur, c’est aussi la possibilité d’utiliser du code existant et de l’exécuter comme une lambda. Imaginez une class Person, qui déclare des comparateurs. Par exemple une méthode compareByAge comme sur l’exemple de Brian Goetz :
class Person { private final String name; private final int age; public static int compareByAge(Person a, Person b) { ... } public static int compareByName(Person a, Person b) { ... } }
Ce principe est simple : il permet d’utiliser certaines méthodes d’une class, dans un contexte similaire à une lambda, mais sans avoir à déclarer le corps d’une fonction. Voici comment nous pourrions alors utiliser les méthodes compareByAge et compareByName :
Person[] people = ... Arrays.sort(people, Person::compareByAge);
Un autre exemple, avec la méthode File.isDirectory, pour par exemple récupérer une liste de dossier :
Files[] folders = dir.listFiles(File::isDirectory()) ;
C’est pour éviter de créer du code, et pour utiliser aussi du code existant.
Sémantique
Une lambda peut capturer les valeurs des variables du scope. Ici, x et y sont capturés par la Lambda.
Callable addOperation(int x, int y) { return () -> x + y; }
Comme vu avant, les variables doivent être final ou effectively final. Les lambdas sont des fonctions anonymes mais pas des objets. Je n’ai pas d’objets, et donc les lambdas ne sont pas des inner class.
Les lambdas et l’api Collection
Lorsque l’on découvre Scala, l’API Collection c’est du caviar. On passe de l’âge de pierre en Java à quelque chose de puissant, simple et facile à lire. Refaire du Java, c’est franchement douloureux. Bon, tu as bien 2 ou 3 pépés qui n’y comprennent rien, mais je pense que la majorité des Java-istes vont monter en compétences sur l’aspect fonctionnel.
Sur l’API Collection en Java, et par rapport aux Lambdas, si tu n’as pas l’APi version 2012, cela n’a aucuns intérêts. En même temps, il serait délicat de réécrire une n-ième API complète… Après les NIO 2 nous aurions les NIO3 avec les Lambdas… you ! hou !
java.util c’est une bonne couche de bon sens, et 2 grosses couches de gras. Il y a beaucoup d’interfaces. Or tu le sais peut-être cher lecteur, mais si tu ajoutes une méthode à une interface, tu forces alors les implémentations à suivre ce nouveau contrat, et tu casses la rétro-compatibilité de ton code. Alors on prend un autre package ? Non. Est-ce que l’on fait des imports statiques comme en C# ? Et bien le gars qui a fait ça en C# est au fond de la baie de San Francisco avec un boulet, tellement les gens trouvent cela débiles. Et si finalement, nous trouvions un moyen de mettre du code dans ces interfaces dis-donc ?
Et bien la réponse est là : on va mettre du code dans les interfaces.
Pour draguer les filles et pour briller en soirée, tu peux utiliser « Mixin » ou « Traits ». Mais bon, avant de faire le kéké, il s’agit déjà de comprendre de quoi l’on parle. Comme dit Rémi « on va pourrir les interfaces ».
Alors les copains qui font du Scala vous parlent de Traits. Ceux qui font du Ruby, vous parlent de Mixins… Bref, chacun y va de son explication.
L’idée est simple : on va pouvoir ajouter du code au niveau de l’interface, code qui sera utilisé si la méthode n’est pas définie. L’héritage multiple ne pose pas de soucis dès lors que l’on regarde les méthodes. Mais sur les champs…
Donc le code sera ajouté dans l’interface, on va ajouter un nouveau mot clé dans Java pour cela.
L’idée : on va pouvoir ajouter du code au niveau de l’interface, qui sera utilisé si la methode n’est pas définie.
L’héritage multiple ne pose pas problème si on regarde les methodes. Mais sur les champs…
Il faut ajouter du code dans l’interface
Une interface en fait va avoir de nouveaux supers pouvoirs. Soit elle est vide et abstraite, comme d’habitude, soit elle définit du code… Et oui ! C’est aussi simple que cela.
interface Iterator { boolean hasNext(); E next(); void remove(); void skip(int i) default { for (; i > 0 && hasNext(); i--) next(); } }
Ajouter une defaut method est compatible binairement !
Ici, la définition d’Iterator est simple. Toute classe qui implémente l’interface hérite de la méthode skip().
D’un point de vue du client, la méthode skipt est une autre méthode virtuelle fournie par l’interface. En invoquant skip sur une instance d’une classe qui implémente Iterator, et qui ne déclare pas de méthode skip(), exécutera le code de l’interface. Si par exemple une classe décide de déclarer une autre implémentation de la méthode skip(), alors il devient possible de le faire simplement.
Lorsqu’une interface étend une autre interface, elle peut changer la définition de la méthode. Si elle souhaite retirer l’implémentation par défaut de la super classe, le mot default none permet alors de supprimer ce code.
Ces défauts methods sont une nouveauté qui ouvre la porte aux lambdas tout en conservant une compatibilité avec l’ancienne API.
Stratégies d’implantations
L’implémentation des lambdas, vous l’imaginez bien, est un sacré travail. Je parle du travail des personnes qui spécifient la JVM et le langage Java. Premier point important : pour des raisons de performances, les personnes qui travaillent sur les lambdas ne veulent pas que tout soit fait par le compilateur. C’est le chemin suivit actuellement par Scala. Cela pose des soucis de lenteur à la compilation.
Bon, je vous le dis discrètement : compiler du code Scala c’est parfois lent. On ne le dit pas trop, ce qui critiquent Scala ont tous mystérieusement disparu.
Bon je balance un autre truc : il n’est pas certain que Scala s’améliore d’ici quelques mois… car les gens qui font Scala n’ont pas la main sur la JVM. Alors que les personnes de la JSR 335 (Brian et Rémi) ont les clés de la voiture. Et c’est pour cette raison que j’imagine Java 8 avec un gros lance-roquette sur l’épaule, entrain de viser les copains, et de les décapiter ses copains. Celui qui croit que Java c’est mort n’a pas très bien compris ce qu’il se passe depuis fin 2010.
Sur la stratégie d’implementation donc, il y a différents systèmes qui permettent d’optimiser l’exécution et d’in-liner proprement le code. Par exemple une lambda qui ne capture pas de valeur du scope doit être constante :
FileFilter filter = file -> file.isDirectory()
Ensuite, les gars de la JSR-335 (et qui sont les mêmes que ceux de la JSR-292) se sont dit : allez, on va utiliser invokeDynamics. Tout le monde n’y a vu que du feu, passe moi le briquet que je leur mette le feu.
invokedynamics : la 1ere fois qu’une methode est appelée, je déclare où se trouve vraiment le code. C’est simplement le moyen d’introduire la notion de pointeur de méthode.
Pour terminer, la lambda c’est une méthode. Ce n’est plus une class. C’est plutôt une bonne nouvelle, car en terme de performances, c’est vraiment plus puissant. Lorsque vous déclarez une class, il y a un overhead lié au chargement et à la vérification. Celui-ci n’existe pas pour les lambdas.
Il y a d’autres secrets, comme l’utilisation de proxy, de dynamic proxy avec la notion de CallSite, la notion de method handle… Tout ceci sera expliqué à Devoxx France par Rémi Forax, qui a promis d’expliquer ce qu’il y aura dans Java 8.
En conclusion : oui nous aurons des lambdas en Java8, et les autres langages auront alors peut-être un peu de souci à se faire.
http://hg.openjdk.java.net/lambda/lambda
Ressources
– proposition sur les Lambdas, article de Brian Goetz
– Pour comprendre les Generics, lisez le chapitre 5 du livre « Effective Java » de Joshua BLOCH.
– lise de discussion sur les Lambdas : http://hg.openjdk.java.net/lambda/lambda
Wow, j’avais complètement oublié avoir parlé d’erasure un jour… En tout cas, chouette article.
Pépé toi même !
Les pépés édentés, tu sais ce qu’ils te disent ? 🙂
Je te réponds en chantonnant deux extraits de chansons de Georges Brassens (un artiste vénéré par les pépés):
1) Le temps ne fait rien à l’affaire
« Quand ils sont tout neufs
Qu’ils sortent de l’œuf
Du cocon
Tous les jeunes blancs-becs
Prennent les vieux mecs
Pour des cons
Quand ils sont d’venus
Des têtes chenues
Des grisons
Tous les vieux fourneaux
Prennent les jeunots
Pour des cons
Moi, qui balance entre deux âges
J’leur adresse à tous un message
Le temps ne fait rien à l’affaire
Quand on est con, on est con »
(il faut prendre « con » ici dans un sens générique/remplaçable et non insultant, évidemment)
Cela, c’est pour illustrer le fait que dans ce métier, si tu ne vas pas au JUG, si tu ne lis pas la prose du Touilleur et de ses illustres compagnons, si tu n’écoutes pas les CastCodeurs, que tu ne te décarcasses pas pour aller à Devoxx France, alors tu te pé-pé-ises à
30 ans (juste 5 ans avant d’être chauve et ridé …).
Scala ne provoque pas l’enthousiasme des foules de java-istes (je ne parle pas de ceux sont qui sont la Javasphère, je parle du Duke moyen).
C’est un fait.
Au lieu de s’en prendre à des pépés édentés qui n’en peuvent mais, il faudrait peut-être voir dans le langage lui-même et dans la façon dont il est « vendu » s’il n’y a pas un début d’explication…
2) « Mourir pour des idées »
« Jugeant qu’il n’y a pas péril en la demeure
Allons vers l’autre monde en flânant en chemin
Car, à forcer l’allure, il arrive qu’on meure
Pour des idées n’ayant plus cours le lendemain
Or, s’il est une chose amère, désolante
En rendant l’âme à Dieu c’est bien de constater
Qu’on a fait fausse route, qu’on s’est trompé d’idée
Mourrons pour des idées, d’accord, mais de mort lente
D’accord, mais de mort lente »
Cela, c’est pour illustrer le temps gagné à ne pas s’engager trop rapidement dans le hype. Pour Scala, cela tombe bien … puisqu’on
nous informe qu’une alternative plus consistante est en approche.
Pour finir, le commentaire du pépé trolleur: Groovy a une bien meilleure approche de la communauté Java (Bonjour Mr Laforge !).
1) on peut grooviser ses jeunes collègues (et même encore plus facilement les vieux; ils gobent tout …) sans qu’ils s’en rendent compte !
2) on peut même faire du devops avec Groovy (faites une tentative avec Scala, juste pour rire …).
Groovy is in: il est temps de virer Python des salles Java.
Ciao a tutti, et un grand merci au Touilleur pour son engagement dans la communauté Java (et pour son billet en particulier, qui est excellent quand il ne réveille pas les séniors assoupis :-)) !
PS: je vais me débarrasser vite fait de mes bouquins sur Scala. Il faut que je trouve un jeune qui va me les acheter les yeux fermés !
Excellent commentaire, merci pour ton retour 😉
Bonjour et merci,
Cet article est intéressant pour ceux qui suivent de loin l’évolution de Java.
Quelques remarques/questions en vrac:
– « Il serait vraiment important d’enseigner les bases de la programmation dès le lycée. »
En théorie, c’est le cas depuis peu. Cf l’algorithmie dans le programme de maths en classe de seconde : http://media.eduscol.education.fr/file/Programmes/17/8/Doc_ress_algo_v25_109178.pdf
– La capture de variables mutables dans les fermetures est complètement impossible ? Même si le développeur en a envie ?
– La remarque sur les extension methods de C# et sa prétendue débilité me parait assez limite. N’en déplaise aux puristes des langages qui crachent sur le sucre syntaxique, le rajout de code dans une interface est loin d’apporter tous les conforts de lisibilité qu’offrent les extension methods… Il suffit de savoir les utiliser à bon escient.
@Oaz
La capture de variables mutables dans les fermetures est assez compliquée à mettre en œuvre en fait, du fait de la mutabilité même de cette dernière.
En Javascript, ils ont trouvé une solution avec leur implémentation de « closure »: la valeur de la variable mutable passée à une closure est égale à la dernière valeur connue dans le contexte d’exécution.
Exemple plus parlant:
var incrementer = function(arg) { var increment = 1; var result = function() { return arg + increment; }; increment = 2; return result(); }
Si l’on appelle incrementer(3), on aura comme résultat 5 et non pas 4 car l’exécution de la closure result() se fera avec la dernière valeur connue de la variable increment dans son contexte, à savoir 2 et non pas 1 comme défini initialement.
Donc tout cela est assez logique si l’on raisonne en terme de pointeur. Tant que la variable « mutable » est accessible dans le contexte d’exécution, sa valeur peut être définie par n’importe quel objet qui a une référence dessus. C’est précisément pour cette raison que les variables finales passées dans les inner class en Java sont converties en constant par le compilateur.
a) FileFilter filter = file -> file.isDirectory();
b) Callable callable = () -> { return fib(17); };
Autant a) sonne bien comme un usage de closure (il n’y a pas de « return »), autant b) pourrait être vu comme du sucre syntaxique pour l’écriture de l’implémentation d’une SAM. Je ne vois pas forcément ici l’intérêt de passer par une closure (vu qu’il est question in fine de créer un objet Callable), ce qui pourrait être intéressant au delà des closures.
On vivra avec de nouvelles formes d’auto-boxing:
* de lambda vers un object SAM: FileFilter filter = file -> file.isDirectory();
* de méthode vers une closure: dir.listFiles(File::isDirectory()) ;
Par contre, je ne vois vraiment pas en quoi l’utilisation de « default » va de pair avec l’introduction des lambdas… Il est loisible d’introduire de nouvelles méthodes associées aux classes Collection de Java: cela permettra d’utiliser les lambdas et la compatibilité ascendante sera tjrs assurée. Bref, en quoi une modification des interfaces Java est nécessaire ?
Sinon sur le fond, j’ai peur que l’utilisation intensive des lambdas avec inférence de type dans le cas des SAM (single abstract method) ne rend le code encore plus obscur pour celui qui n’a pas écrit…
Effectivement si on a une lambda du genre:
MyInterface lambda = (input1,input2) -> {return input2.doSomeThing(input2);}
Franchement, qui peut dire juste en lisant le code le type de input1, input2 et du retour attendu sans aller voir vraiment l’interface MyInterface ?
@Duy Hai,
Merci pour l’info sur Javascript.
En fait, je connais trop peu Java et je n’avais pas intégré que les classes anonymes ne pouvaient capturer que des constantes. Du coup, je comprends la limitation sur les fermetures et j’ai essayé un exemple où on capture un tableau. Visiblement, on peut modifier les éléments d’un tableau « final » et donc introduire des valeurs mutables au besoin… Je suppose que cela sera pareil avec les lambdas…
Oaz je me suis posé la question et l’utilisation de final n’ai rien à voir avec l’immutabilité de la variable.
Voir:
http://stackoverflow.com/questions/7076773/why-do-we-use-final-keyword-with-anonymous-inner-classes
@S.Lorber
Oui, merci pour la précision. C’est bien ce que j’avais compris : la classe anonyme capture une copie de la variable locale mais cette copie n’est autorisée que pour les variable « final » de manière à ne pas introduire un comportement incohérent où la variable d’origine serait modifiée après avoir été copiée.
Pour la petite histoire, en C#, les variables locales capturées par les fermetures sont allouées dans le tas, et non pas dans la pile, ce qui permet d’autoriser les mutations après capture.
Concernant la roadmap des futures versions de java, il me semble que c’est plutôt Java 8 pour 2013 et java 9 pour 2015. Les dates citées ici (2012 et 2013) c’était la tout première annonce de roadmap d’Oracle avant la sortie de la version 7.