Java 8 : complexify all the things!
- Notes sur la programmation orientée objets
- Les interfaces sont-elles un mauvais patron de conception ?
- Les bonnes et les mauvaises critiques sur Java — Épisode 1 : Les mauvaises critiques
- Les bonnes et les mauvaises critiques sur Java — Épisode 2 : La syntaxe
- Les bonnes et les mauvaises critiques sur Java — Épisode 3 : Les « bonnes » pratiques
- Java 8 : complexify all the things!
- L'inférence de type en Java : on continue de faire n'importe quoi, yaye !
Des fois, je me dis que l’objet des réunions de chez Oracle, ça doit être quelque chose comme : « augmentation de la complexité ». J’imagine bien un truc du genre : tout le monde fume un bon gros bédo avant d’entamer les discussions sur le premier sujet : « reprendre l’implémentation des fonctions anonymes de Groovy et bien la pourrir pour la rendre à chier ». Et faut avouer que, dans leur domaine, ils sont plutôt bons, ces cons !
Récemment, nos projets au boulot ont commencé à migrer vers Java 8 et j’ai enfin pu mettre les mains dans les lambdas et les opérations map, filter, reduce sur les listes. Ben je m’attendais pas à grand-chose et ils ont quand-même trouvé le moyen de me faire halluciner…
Lambdas Canada Dry (que ça ressemble aux lambda mais qu’en fait, ç’en est pas)
Donc j’ai pu essayer les lambdas et j’ai flairé le truc pas net très vite. Vous voyez, l’une des utilisations des lambdas que je fais souvent, moi, c’est d’isoler un bout de code d’une fonction dans un objet pour augmenter la lisibilité. Par exemple pour parcourir les résultats d’un requête en base de données en Android, on utilise un objet
Cursor
getColumnIndex(String columnName)
pour obtenir l’index de la colonne à partir de son nom dans la base de données puis getInt(int columnIndex) ,
getString(int columnIndex)
, etc. pour récupérer les données. Bon, ben quand on a un object complexe, ça fait vite chier de taper
cursor.getString(musicCursor.getColumnIndex(columnName))
à tout bout de champ surtout que quand on a des noms de colonnes un peu longs, ça dépasse vite les 120 caractères.
Du coup, pour améliorer la lisibilité, on stocke ce petit bout de code quelque part, dans une lambda pour pouvoir l’appeler plus tard. Petit exemple dans Phœbius, ici :
def musicCursor = getAppContext() .contentResolver.query(MUSIC_URI, null, null, null, null) // Attention, lambda declaration incoming def getString = { String columnName -> return musicCursor.getString( musicCursor.getColumnIndex(columnName)) } def getInt = { String columnName -> return musicCursor.getInt( musicCursor.getColumnIndex(columnName)) } def getLong = { String columnName -> return musicCursor.getLong( musicCursor.getColumnIndex(columnName)) } if(musicCursor?.moveToFirst()) { while(musicCursor.moveToNext()) { this.vSongs << new Song( getLong(SONG_ID), getString(SONG_TITLE), getString(SONG_ARTIST), getString(SONG_ALBUM), getInt(SONG_NUMBER), getInt(SONG_YEAR) ) } } musicCursor.close()
Moi, j’aime bien ça, les lambdas. Déjà, je trouve le concept génial de pouvoir stocker un bout de code dans une variable, et puis ensuite, c’est super sexy de pouvoir déclarer des fonctions anonymes en paramètre d’une méthode. Vous voyez pas l’intérêt ? Ok, j’en appelle à la puissance de la démonstration !
Comment qu’on trie une liste en Java 7 ?
List<LaClasseAmericaine> laClasseAmericaineList = new ArrayList<>(); laClasseAmericaineList.sort(new Comparator<LaClasseAmericaine>(){ @Override int compare(LaClasseAmericaine laClasseAmericaine, LaClasseAmericaine t1) { if(laClasseAmericaine.derniersMots.equals("monde de merde")) return -1; else return 1; } });
Alors qu’en Groovy, avec une closure, je peux faire :
List<LaClasseAmericaine> laClasseAmericaineList = new ArrayList<>(); laClasseAmericaineList.sort{ it.derniersMots == "Monde de merde" ? 1 : -1 }
C’est joliiiiiiiii <3
Du coup, j’étais content de passer en Java 8. J’allais pouvoir mettre plein de lambdas partout et vivre heureux dans le monde merveillleux des fonctions anonymes ! Et puis j’ai voulu stocker ma première fonction anonyme dans une variable. Alors j’ai commencé à chercher quel objet représentait une lambda. Ça devait forcément être un truc qui s’appelle
Closure
comme en Groovy ou
Delegate
comme en C♯ ou
Function
comme en C++. Bon, en Groovy y’a pas besoin de typer les closures donc la syntaxe devait ressembler à la déclaration du C++
function <double(double)> f
ou celle du C♯
delegate double f(double);
Ah oui, voilà : c’est Function. Attendez… C’est quoi ce truc bizarre…
R apply(T t) Applies this function to the given argument.
Putain, c’est quoi, cette arnaque ? Pourquoi y’a qu’un seul argument, là ? Je fais comment pour définir une lambda avec plusieurs arguements ? Ok faut que je trouve la doc, il doit bien y avoir moyen de déclarer une signature de méthode inline… Je veux dire… Ils ont quand-même pas osé…
Functional interfaces. The Runnable interface—like the Callable<T> interface, the Comparator<T> interface, and a whole host of other interfaces already defined within Java—is what Java 8 calls a functional interface: it is an interface that requires exactly one method to be implemented in order to satisfy the requirements of the interface. This is how the syntax achieves its brevity, because there is no ambiguity around which method of the interface the lambda is trying to define.
Une interface… Il… Il faut déclarer une putain d’interface pour pouvoir déclarer une fonction anonyme…
…
…
C’EST QUOI VOTRE PUTAIN DE PROBLÈME AVEC LES INTERFACES, BORDEL !? C’EST QUOI LE PUTAIN D’INTÉRÊT D’AJOUTER LES LAMBDAS AU LANGAGE SI C’EST POUR DEVOIR DÉCLARER DES INTERFACES À LA CON !? VOUS AVIEZ PAS SUFFISAMMENT DE POSSIBILITÉS SYNTAXIQUES POUR LES TYPER ? IL FAUT FORCÉMMENT VENIR M’EMERDER AVEC DES PUTAINS D’INTERFACE DE PARTOUT !? JE HAIS LES INTERFACES !!!!!§§§ ALLLEZ TOUS CREVER EN ENFER !!!§§!!§ GRAAAAAAAAAAAAAAA !!!!!!!!!!
Ok, on se calme…
Donc, en Java 8, nous avons le droit à une implémentation foireuse des fonctions anonymes, qui sont, donc, pas anonymes du tout parce qu’elles sont nommées dans une interface. On ne peut donc pas déclarer de lambda sans créer une PUTAIN D’INTERFACE DE MES COUILLES JEVAISTOUSLESCREVERETLESFAIREBRÛLERDANSDELACIDEGROUABLABLAALABLLAAAAAAAAAAAA!!!!
Putain >.<
Bref… Sans le côté, fonction jetable, ça limite quand-même sévèrement l’intérêt du truc…
On aurait pu faire autrement ?
Bah ouais, un peu ! Et ça aurait pu être facile, même. Je veux dire : c’est pas pour ce que c’était compliqué de s’inspirer de la syntaxe du C++. Je sais que la communauté Java déteste le C++ qu’elle considère comme un langage dangereux et arriéré, mais quand on en arrive à un tel niveau de dogmatisme, faut commencer à voir un psy ! C’est le Ku Klux Klan de la syntaxe, cette communauté ! Mode Java Power et préservation de la pureté syntaxique !
En fait, j’aurais dû m’en douter que ça ne pouvais qu’être de la merde. Pourquoi ? Parce que Java 8 n’a pas introduit la surcharge des opérateurs. J’explique : finalement, qu’est-ce qu’une fonction anonyme ? Une variable contenant un bout de code, que l’on peut appeler comme une fonction. Autrement dit : c’est un objet. Un objet qui est un objet et qu’on manipule comme un objet. À la différence qu’on peut l’appler comme un fonction. Mais comment telle sorcellerie est possible !?
En fait, c’est très simple, si l’on considère un couple de parenthèses
()
comme un opérateur. Du coup, une fonction anonyme, c’est un objet auquel on peut appliquer l’opérateur
()
. Mais un opérateur n’est, après-tout, lui-même qu’une fonction ! Par exemple, prenons l’opérateur binaire
+
appliqué à deux entiers
a
et
b
. Écrire
a + b
pourrait parfaitement s’écrire
a.add(b)
. Hé bien de la même manière, l’opérateur
()
appliqué à l’objet
a
pourrait s’écrire comme
a.call()
. C’est comme ça que fonctionne Groovy.
Mais laissez-moi vous présenter Bob. Bob est un spécialiste du langage Java. C’est lui qui sera chargé de vous expliquer pourquoi Java est aussi à chier.
Salut !
Ta gueule. Donc, Bob, dis-nous pourquoi il y a besoin de créer des interfaces à la con pour créer des lambdas ?
Hé bien c’est à cause de la nature fortement typée du langage. Tu vois, il fallait trouver un moyen de déclarer la signature de la méthode. Tu nous as parlé de Groovy, mais Groovy est un langage dynamique. Ce qui fonctionne en Groovy n’aurait pas pu fonctionner en Java.
Et le C++, c’est un langage dynamique, tête de bite ? Ça aurait été trop dur d’ajouter une nouvelle syntaxe pour déclarer des signatures inline ? Ça vous gêne pas, d’habitude ? Ça vous a pas autant emmerdé quand vous avez ajouté les
enum
à Java 5 ?
Ben, c’est toujours un peu compliqué d’ajouter une nouvelle syntaxe à Java. Tu vois, c’est un peu des gros casse-couilles et ça fait toujours des débats à rallonge pour rajouter une nouvelle fonctionnalité.
Tu métonnes. Mais dis-moi, trou du cul, tu sais comment ils font ça en Groovy ?
Nan. Dis-moi ?
Alors, en Groovy, c’est assez simple. Toute fonction anonyme est représentée par la classe
Closure<V>
. Un
Closure
est un objet qui possède une méthode
call()
et une méthode
call(Object... args)
qui sont la surcharge de l’opérateur
()
respectivement sans arguments et avec arguments. Le type de retour de ces deux méthodes est spécifié par le générique de l’objet. Tu vois, si Java 8 avait ajouté la surcharge des opérateurs à ses objets, les choses auraient été beaucoup plus simples.
Oui mais y’a un problème
Lequel ?
Hé bien, la signature de la méthode
call(Object... args)indique que c’est un tableau d’
Objectqui est passé en paramètre. Ce signifie que, tu perds le type des paramètres à l’appel.
Et alors, face de cul ? Il suffit de préciser le type à la déclaration :
Function<Integer> f = (String s, Integer i) -> Integer.valueOf(s) + i;
Peut-être, mais si tu veux définir une signature sans déclarer d’objet ?
Facile aussi :
LaFonctionAméricaine extends Function<Void> { @Parameters({String.class, Integer.class}) public void call(Object... args); }
Il ne reste plus au compilo qu’à décompacter le tableau et à transtyper comme il faut.
Nan mais c’est pas possible, ça.
Pourquoi ?
Parce Java ne devine pas le type des objets à l’exécution.
Ah ouais ? Alors dis-moi, quand je fais ça :
List<Integer> integers = new ArrayList<Integer>();
, mes objets dans ma liste, ils sont de quel type ?
Hmm… C’est compliqué. À quel étape ? À l’exécution ou à la compilation ?
Qu’est-ce qu’on s’en branle ? C’est bien la même chose, non ? C’est pas ce que tu viens de dire ?
Ben… C’est plus compliqué que ça… En fait, à la compilation, c’est une liste d’
Integermais, à l’exécution, c’est une liste d’
Object.
Exactement. On appelle ça le type erasure. C’est un mécanisme qui permettait à Java 5 de rester compatible avec Java 4 qui ne proposait que des listes d’
Object
tout en permettant d’y mettre tout et n’importe quoi sans avoir à transtyper comme un barbare. Et ça fonctionne comment ? Hé bien tout simplement, le compilateur insère des type cast là où il faut pour que les listes continuent de stocker des
Object
tout en laissant l’utilisateur manipuler d’autres choses. Alors maintenant, dis-moi, imbécile, elle est où, la différence avec ma solution ?
…
Exactement. Et sinon, t’as pu utiliser map, filter, reduce sur les listes dans Java 8 ?
Une opération de filtrage des listes qui renvoie une liste ? Trop simple…
Une autre nouveauté de Java 8 que j’avais hâte de tester et qui venait avec les lambdas, ce sont les opération de transformation et de filtrage sur les listes. Je vous avait déjà parlé, dans une précédente série d’articles, de la tendance des développeurs Java à multiplier les objets. Ben pour les opération sur les listes, ça a pas loupé, il a fallu qu’ils créent un putain d’objet intermédiaire pour appliquer les opérations : le
Stream
… Bon, dans les faits, c’est pas spécifiquement con, l’idée du
Stream
, c’est de fournir une API qui permette de calculer des opérations en parallèle sur les liste.
Sauf que je devrais pas avoir affaire avec !
L’idée de
map()
, par exemple, c’est de prendre une liste et d’appliquer une opération sur les éléments de cette liste pour en créer une nouvelle. J’ai une liste en entrée, je veux une liste en sortie. C’est simple ! C’est clair !
Alors, oui, mais le développeur de chez Oracle, lui, il emmerde ta clareté…
Petit exemple de filtrage de liste en Java 8 :
List<Integer> integers = new ArrayList<>(); integers = integers.stream().map(integer -> integer + 6)
Voilà, je veux juste ajouter 6 à tous les entiers de ma liste.
Oui mais là, ça va pas compiler.
Putain, t’es encore là, toi ? Oui, je le vois bien que ça va pas compiler. Y’a un problème de typage.
map()
ne renvoie pas une liste, comme il devrait, mais un
Stream
. C’est complètement con ! C’est pas comme ça que ça devrait fonctionner ! Comment je récupère ma liste, moi, maintenant !?
Nan mais c’est simple : il suffit de rajouter
.collect(Collectors.toList())derrière.
Attends, c’est quoi, ton histoire, là ? Ça fait quoi, ça,
.collect(Collectors.toList())
?
/** * Returns a {@code Collector} that accumulates the input elements into a * new {@code List}. There are no guarantees on the type, mutability, * serializability, or thread-safety of the {@code List} returned; if more * control over the returned {@code List} is required, use {@link #toCollection(Supplier)}. * * @param <T> the type of the input elements * @return a {@code Collector} which collects all the input elements into a * {@code List}, in encounter order */ public static <T> Collector<T, ?, List<T>> toList() { return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add, (left, right) -> { left.addAll(right); return left; }, CH_ID); }
O___O
Attends, t’es sérieusement, sérieux, là !? Qu’est-ce que c’est que cette bouillasse !? Pourquoi c’est aussi compliqué !? UN LANGAGE DE PROGRAMMATION, ÇA DEVRAIT ÊTRE SIMPLE À UTILISER ! VOUS ÊTES TOUS DES INCAPABLES ! VOUS ALLEZ TOUS BRÛLER EN ENFER ! AAARARRAAGAGAGAAARRARRAGGGAGGGGG !
Honnêtement, je comprends pas ce qui traverse la tête des ingénieurs de chez Oracle. Et c’est pas comme si c’était compliqué de faire simple. Prenez Groovy, par exemple. Faire un map sur une liste est une opération extrêment simple :
[12, 16, 42].collect{ it + 6 }
. Pareil pour faire un filter :
[12, 16, 42].findAll{ it == 12 }
.
C’est simple, bordel…
Addendum
On a trouvé, dans les commentaires, que je donnais des exemples à la con. Voici un exemple ablsolument pas trivial tiré du boulot. Il repose sur une
HashMap
Range
et de
BigDecimal
. Il s’agit de rechercher le réel qui est associée à l’ensemble de valeurs entières qui contient un entier passé en paramètre. En Java, ça donne ça :
public BigDecimal getValue(Integer intValue){ Optional<Map.Entry<Range<Integer>, BigDecimal>> e = map.entrySet() .stream().filter(entry -> entry.getKey().contains(intValue)).findFirst(); return e.isPresent() ? e.get().getValue() : BigDecimal.ONE; }
En Groovy, ça donne ça :
public BigDecimal getValue(Integer intValue){ map.find {it.key.contains(intValue)}?.value ?: BigDecimal.ZERO }
Voilà, voilà… Question lisibilité, c’est autre chose, quand-même…
Déjà 20 avis pertinents dans Java 8 : complexify all the things!
Les commentaires sont fermés.
Merci.
Il est rigolo ton article, j’ai laissé les fautes ça fémieu !
Tcho !
Tu vas le partager sur le journal du hacker ?
Très chouette comme article.
En fait, j’ai eu envie de me remettre à Java… (à mon avis je suis maso :-))
Sur le fond, je crois que les gars de Java ont des impératifs… et des choix de stratégie.
Sinon, je trouverai chouette, vu ton niveau de compréhension du langage, de proposer une analyse de bout de code ou d une bonne pratique de programmation. Car en fait, il est rare de trouver en français des articles pointus en Java ou en Groovy.
J’ai en tête le genre d’article que l’on trouve sur Python sur http://sametmax.com/ (je parle évidemment des articles sur le code et non le cul :-))
J’attends avec impatience ton prochain article ou coup de gueule documenté.
Merci
Cloug
Ouais c’est fait mais comme je suis une pourriture, j’ai ajouté le tag humour hé hé hé.
Tcho !
Perso je ne suis pas du tout d’accord avec tout cela…
L’article m’a même fait bondir et j’ai l’impression qu’il n’y a aucun recul sur la critique, voir presque de la mauvaise fois.
Pour ceux que çà intéresse J’ai fait une réponse plus détaillé à cette adresse (pour me simplifier la saisie du code) : http://www.developpez.net/forums/blogs/1195-adiguba/b1308/cest-bien-beau-taper-java/
a++
Pourquoi, « heureusement » ? En quoi c’est mieux de faire comme ça ? Le semblant de justification que je trouve, c’est :
Donc en gros, c’est bien parce que c’est une solution à moitié finie qui se repose sur ses acquis ? D’ailleurs, rien n’aurait empêché de faire une association un pour un entre les closures et les interfaces fonctionnelles. Sauf que ce qu’on a là, ce ne sont pas des lambdas. C’est un tout petit sucre syntaxique pour déclarer des classes anonymes. Ça retire tout l’intérêt des lambdas, ça rend le langage encore plus verbeux (ce que j’ai déjà dénoncé ici) en obligeant le développeur à passer encore par des interfaces (une manie que j’avais déjà dénoncé là). Résultat : toujours pas moyen de faire des callbacks proprement. Aucun intérêt…
En ce qui concerne ta partie sur les streams, tu tombres aussi à côté de la plaque :
C’est bien, mais ça ne change rien au fait que je ne devrais pas avoir affaire avec. Je n’ai pas dit que les streams étaient mauvais, juste que ça complexifie inutilement les choses. Et le cas que tu donnes est un cas extrêment spécifique. Les rares fois où tu as besoin de faire des enchaînements de traitement sur des collections de données, c’est lorsque tu travailles sur des bases de données. Et en ce qui me concerne, si tu as besoin de faire des gros post-traitements sur des données après des requêtes en base, c’est que ta requête est à chier.
Quitte à faire une API d’interrogation sur des données, ils auraient pu s’inspirer du LINQ de C#. Mais non… Après tout, pourquoi s’inspirer d’un langage d’interrogation de données éprouvé qui existe depuis plus de 20 ans !? Réinventer la roue, c’est une spécialité des devs Java…
Ce que je dénonce dans cet article, c’est la complexité et la verbosité du langage. Le fait que ses concepteurs soient positivement incapables d’ajouter une fonctionnalité qui soit simple et intuitive. Devoir écrire une interface fonctionnelle pour pouvoir déclarer une lambda, ça n’est pas simple, ça n’est pas intuitif. Devoir appeler moi-même la fonction qui retraduit un stream en la collection de départ, ça n’est pas simple, ça n’est pas intuitif.
J’ai une très bonne compréhension du fonctionnel. Je m’en suis servi dans des projets en JavaScript, en Ruby, en OCamL, en C++ ou en Groovy. Et j’ai pu passer de l’un à l’autre sans jamais trop de peine. Les acquis que j’avais pour un langage étaient valables pour un autre. En Java, j’en ai chié comme pas permis pour faire des trucs bdons comme un map sur un dictionnaire. Et c’est pas comme s’ils avaient pas réussi à faire plus simple parce que même Guava est plus intiutif.
– Pas de type-function complètement inutile et qui fait doublon.
– Compatibilité maximum avec tous les codes existant sans avoir à modifier ou recompiler quoi que ce soit.
– Et cela en proposant exactement les mêmes fonctionnalités.
S’ils avaient opté pour des delegates comme C#, cela aurait été limité à quelques API conçus spécifiquement pour cela.
Le tout sans rien apporté de plus…
Tu sembles avoir avoir un problème avec le fait de déclarer une interface…
Pourtant le fait de déclarer un delegate cela revient exactement au même, mais implique souvent de dupliquer le type avec une interface…
Sinon tu affirmes désormais que ce ne sont pas des lambdas : pourquoi donc ???
Parce que cela me semble juste une affirmation lancé en l’air sans rien derrière…
Quand à mon exemple sur les Streams, il me semble plus pertinent qu’ajouter « +6 » à tous les éléments…
L’intérêt des Streams ce n’est pas de modifier une collection, c’est surtout d’écrire des traitements divers qui peuvent se paralléliser facilement.
Tout comme la source de données, le résultat n’est pas forcément une liste ou une collection, donc oui il faut le préciser.
Enfin tu dis que tu en a chier pour faire un « map », mais je t’ai bien montré que cela pouvait se faire en une ligne avec replaceAll().
Désolé si tu sembles mal prendre la critique…
a++
Ce que je reproche à Java dans cet article est ce que je reproche Java depuis toujours : la syntaxe est lourde et verseuse. Leur implémentation des lambdas est tellement pas autosuffisante qu’ils ont dû faire tout un package avec des fonctions de base.
Pour replaceAll, c’est top, mais ça génère des effets de bords. Donc c’est un idée à la con. Si je veux modifier la liste, je suis assez grand pour la modifier dans ma callback ou la réaffecter à la nouvelle liste générée. Là, j’ai pas le choix.
Je ne prends pas mal ta critique, c’est au-delà de ça : je la trouve complètement hors de propos et j’ai, moi, pour le coup, l’impression d’avoir affaire à un Java fan prêt à toute la mauvaise foi possible pour défendre son langage.
Ce que tu n’arrives pas à comprendre, c’est que les interfaces fonctionnelles et les delegates c’est la même chose : un type avec une seule méthode permettant de décrire le prototype de la fonction-lambda.
Donc du coup ton affirmation « ce ne sont pas des lambdas » pourrait s’appliquer aussi à C#… et ca reste tout autant stupide !
Le fait d’utiliser des delegates à la place des interfaces fonctionnelles n’aurait rien apporté de plus, si ce n’est une incompatibilité avec l’immense majorité des APIs existante qui utilisaient déjà cela de facto.
Et maintenant tu arrives même à reprocher l’existence d’un package avec des fonctions.
C’est comme si tu critiquais l’existence de plusieurs delegates dans l’API standard de C#…
Et désolé si tu n’arrives pas à comprendre que l’API de Stream ce n’est pas simplement TA méthode map() qui retourne une liste, mais quelque chose de bien plus complet et puissant que cela basé sur l’abstraction du flux (design pattern MapReduce pour plus de détail).
Alors oui du coup ce n’est pas un simple appel qui fait exactement ce que TOI tu veux… mais cela permet de le faire en 3 lignes de codes, et bien plus encore.
Enfin non je ne suis pas « prêt à toute la mauvaise foi possible pour défendre mon langage ».
Ca me fait marrer de te voir dire cela, surtout quand je relis ton « joliiiiiiiii » exemple du sort() en Groovy… et que tu omets complètement d’indiquer le code équivalent en Java.
Du coup : ne serais-tu pas « prêt à toute la mauvaise foi possible pour conspuer un langage » ???
Et que dire de ta comparaison en bois entre delegate double f(double) et le type Function !
L’interface Function de Java devrait plutôt être comparé au delegate Func de C#.
Ce qui nous donne coté C# :
Et en Java :
Et quand à ton exemple minimaliste :
Tu aurais dû le comparer avec une interface équivalente :
Mais problème ca colle pas avec ton propos, car on voit bien que les deux syntaxes sont très proche.
La seule différence c’est qu’en Java on donne un nom explicite à la fonction…
En venant ici le « java fan » pensait trouver des critiques constructives… Il n’en est rien.
Je n’y ai même pas trouvé mention des deux plus gros reproches que je fais à Java 8 concernant (indirectement) les lambdas :
– l’absence de type-value qui oblige à démultiplier les prototypes de fonctions.
– la notion même de checked-exception qui devient encore plus lourdingue avec les lambdas.
a++
Il faut lire ceci (en espérant que cela marche) :
Ce qui nous donne coté C# :
Et en Java :
Je vais juste t’indiquer que tu te focalise sur l’implémentation des delegates alors que je ne l’ai cité que pour l’exemple syntaxique aux côtés des exemple de Groovy et C++. Et je l’ai cité pour mettre en exergue la lourdeur du langage. C#, Groovy et C++ ne m’obligent pas à créer une interface si je souhaite déclarer une nouvelle signature. Je peux le faire inline.
C’est ça, et pas autre chose que je reproche. Si on reprend ton exemple du package de delegates en C#, ce n’est pas leur existence que je critique mais le fait qu’ils auraient été obligés de les écrire pour fournir des fonctions de base. L’implémentation de Java n’est pas autosuffisanre parce que je ne peux pas déclarer une signature inline et je suis obligé de passer par la déclaration d’une pseudo-classe pour utiliser une lambda dans le code. Ce n’est pas comme ça que les lambdas devraient fonctionner.
Mais puisque tu veux donner des exemples, voici un exemple tiré d’un projet de traitement d’images en C++ :
En Groovy:
En Java, d'abord déclarer l'interface
Puis l'utiliser:
Et ça, c'est sans te parler du fait que l'interface, je peux pas la déclarer dans une fonction juste pour stocker du code, hein !
Mais c'est vrai, après tout, la lisibilité du code, qu'est-ce qu'on s'en branle !?
Si tu n'est pas capable de comprendre ça, alors je ne peux plus rien pour toi.
Curieusement ça tombe mal parce qu’on se retrouve dans le même cas que Java : il faut que le delegate soit déclaré avant de l’utiliser.
Tu n’arrêtes pas d’opposer les lambdas de Java à celle de C#, alors qu’ils fonctionnent sur le même principe.
Donc pour être cohérent tu aurais dû opposer Java/C# à Groovy/C++.
Maintenant perso je ne vous rien d’hallucinant à ce que des langages fortement typés comme Java ou C# t’impose de typer fortement tes lambdas.
a++
Je n’ai cité le C# que comme une possibilité syntaxique. C’est toi qui t’es formalisé dessus. Mais si tu veux que je parle des delegates de C#, y’a une grande différence syntaxique avec les interfaces à la con de Java : je peux déclarer une putain de delegate où ça me chante et pas juste dans le corps d’une classe ou un fichier à part.
Mais encore une fois : la lisibilité, la simplicité, chez Java, on leur chie dessus…
Mais ceci dit, à ce stade du débat, venant de quelqu’un qui me sort
Pour justifier que Java ne se soit pas inspiré, au pif, du C++, que je cite tout le long du billet, je ne m’attends plus à rien…
Et je ne t’ai jamais reproché la comparaison avec C++ nulle part. D’ailleurs j’ai uniquement basé mes exemples sur le C#…
Mais il y a un gros problème dans ton résonnement puisque le fonctionnement des lambdas de C# est similaire à celui de Java, mais là cela ne semble pas te poser de problème :
Java : interface fonctionnelle obligatoire, et namespace en contenant plein => Mon dieu c’est une catastrophe !!
C# : type delegate obligatoire, et namespace en contenant plein => OK
a++