API Asynchrones en Java 8

27 662 vues

Publié le

Slides de ma présentation à Devoxx France 2015.

Publié dans : Formation

API Asynchrones en Java 8

  1. 1. @JosePaumard asynchrones Java
  2. 2. #J8Async @JosePaumard Asynchrone ?
  3. 3. #J8Async @JosePaumard AsynchroneAsynchrone • Trois tâches à exécuter T1 T2 T3
  4. 4. #J8Async @JosePaumard AsynchroneAsynchrone • 1ère façon de faire : « exécution synchrone »
  5. 5. #J8Async @JosePaumard AsynchroneAsynchrone • 2ème façon de faire : « exécution multithread »
  6. 6. #J8Async @JosePaumard AsynchroneAsynchrone • 2ème façon de faire : « exécution multithread » … sur un seul cœur
  7. 7. #J8Async @JosePaumard AsynchroneAsynchrone • 3ème façon de faire : « asynchrone »
  8. 8. #J8Async @JosePaumard AsynchroneAsynchrone • 3ème façon de faire : « asynchrone » … même sur un multicœur
  9. 9. #J8Async @JosePaumard AsynchroneAsynchrone • 3ème façon de faire : • Plus rapide ?
  10. 10. #J8Async @JosePaumard • 3ème façon de faire : • Plus rapide ?  En général oui  Approche « non blocking » AsynchroneAsynchrone
  11. 11. #J8Async @JosePaumard • Différence avec le modèle synchrone multithread ? 1) Le traitement décide de passer d’une tâche à l’autre 2) Pas de problème d’atomicité / visibilité • Performances ?  Pas de « context switch » AsynchroneAsynchrone
  12. 12. #J8Async @JosePaumard • Pattern AsynchroneAsynchrone queryEngine.select("select user from User") .forEach(user ‐> System.out.prinln(user)) ;
  13. 13. #J8Async @JosePaumard • Pattern • Callback ou tâche : lambda expression AsynchroneAsynchrone queryEngine.select("select user from User") .forEach(user ‐> System.out.prinln(user)) ;
  14. 14. #J8Async @JosePaumard • Pattern • Callback ou tâche : lambda expression • Enchaînement : lorsque le résultat est disponible alors on enchaîne avec le traitement AsynchroneAsynchrone queryEngine.select("select user from User") .forEach(System.out::prinln) ;
  15. 15. #J8Async @JosePaumard • Pattern • Callback ou tâche : lambda expression • Enchaînement : lorsque le résultat est disponible alors on enchaîne avec le traitement • Comment écrire ceci en Java ? AsynchroneAsynchrone queryEngine.select("select user from User") .forEach(System.out::prinln) ;
  16. 16. #J8Async @JosePaumard Notion de tâcheNotion de tâche • Depuis Java 1 : Runnable • Java 5 : Callable • Java 5 : ExecutorService (pool de threads) • On donne une tâche, on récupère un Future
  17. 17. #J8Async @JosePaumard Notion de tâcheNotion de tâche • Pattern Callable<String> task = () ‐> "select user from User" ; Future<String> future = executorService.submit(task) ;
  18. 18. #J8Async @JosePaumard Notion de tâcheNotion de tâche • Pattern Callable<String> task = () ‐> "select user from User" ; Future<String> future = executorService.submit(task) ; List<User> users = future.get() ;    // blocking users.forEach(System.out::println) ;
  19. 19. #J8Async @JosePaumard Notion de tâcheNotion de tâche • Pattern • Le passage d’un objet d’une tâche à l’autre se fait dans le thread « maître » Callable<String> task = () ‐> "select user from User" ; Future<String> future = executorService.submit(task) ; List<User> users = future.get() ;    // blocking users.forEach(System.out::println) ;
  20. 20. #J8Async @JosePaumard Programmation asynchroneProgrammation asynchrone • Nouveaux outils en Java 8 pour traiter ce point • Solution pour enchaîner les tâches • Asynchrone & multithread
  21. 21. #J8Async @JosePaumard Questions ? #J8Async Questions ? #J8Async
  22. 22. #J8Async @JosePaumard • Nouvelle interface en Java 8 : CompletionStage De quoi s’agit-il ?De quoi s’agit-il ? /** * A stage of a possibly asynchronous computation, that performs an * action or computes a value when another CompletionStage completes. * A stage completes upon termination of its computation, but this may * in turn trigger other dependent stages. */
  23. 23. #J8Async @JosePaumard • Nouvelle interface en Java 8 : CompletionStage • CompletionStage = une tâche qui se déclenche sur une autre et qui peut en déclencher d’autres De quoi s’agit-il ?De quoi s’agit-il ? /** * A stage of a possibly asynchronous computation, that performs an * action or computes a value when another CompletionStage completes. * A stage completes upon termination of its computation, but this may * in turn trigger other dependent stages */
  24. 24. #J8Async @JosePaumard • Classe d’implémentation : CompletableFuture • Implémente à la fois :  Future  CompletionStage De quoi s’agit-il ?De quoi s’agit-il ?
  25. 25. #J8Async @JosePaumard • CompletableFuture : modélise une tâche • Peut être dans trois états : De quoi s’agit-il ?De quoi s’agit-il ?
  26. 26. #J8Async @JosePaumard • CompletableFuture : modélise une tâche • Peut être dans trois états :  En train d’être calculée De quoi s’agit-il ?De quoi s’agit-il ?
  27. 27. #J8Async @JosePaumard • CompletableFuture : modélise une tâche • Peut être dans trois états :  En train d’être calculée  Calculée, ayant produit un résultat De quoi s’agit-il ?De quoi s’agit-il ?
  28. 28. #J8Async @JosePaumard • CompletableFuture : modélise une tâche • Peut être dans trois états :  En train d’être calculée  Calculée, ayant produit un résultat  Calculée, ayant généré une exception De quoi s’agit-il ?De quoi s’agit-il ?
  29. 29. #J8Async @JosePaumard FutureFuture • Cinq méthodes : boolean cancel(boolean mayInterruptIfRunning) ;
  30. 30. #J8Async @JosePaumard FutureFuture • Cinq méthodes : boolean cancel(boolean mayInterruptIfRunning) ; boolean isCanceled() ; boolean isDone() ;
  31. 31. #J8Async @JosePaumard FutureFuture • Cinq méthodes : boolean cancel(boolean mayInterruptIfRunning) ; boolean isCanceled() ; boolean isDone() ; V get() ; // blocking call  V get(long timeout, TimeUnit timeUnit) ; // may throw a checked exception throws InterruptedException, ExecutionException, TimeoutException ;
  32. 32. #J8Async @JosePaumard CompletableFutureCompletableFuture • Méthodes de type « future » : V join() ; // may throw an unchecked exception V getNow(V valueIfAbsent) ; // returns immediately
  33. 33. #J8Async @JosePaumard CompletableFutureCompletableFuture • Méthodes de type « future » : V join() ; // may throw an unchecked exception V getNow(V valueIfAbsent) ; // returns immediately boolean complete(V value) ;  // sets the returned value is not returned void obtrudeValue(V value) ; // resets the returned value 
  34. 34. #J8Async @JosePaumard CompletableFutureCompletableFuture • Méthodes de type « future » : V join() ; // may throw an unchecked exception V getNow(V valueIfAbsent) ; // returns immediately boolean complete(V value) ;  // sets the returned value is not returned void obtrudeValue(V value) ; // resets the returned value  boolean completeExceptionnaly(Throwable t) ;  // sets an exception void obtrudeException(Throwable t) ; // resets with an exception
  35. 35. #J8Async @JosePaumard Création d’un CompletableFutureCréation d’un CompletableFuture • CompletableFuture déjà terminé public static <U> CompletableFuture<U> completedFuture(U value) ;
  36. 36. #J8Async @JosePaumard Création d’un CompletableFutureCréation d’un CompletableFuture • CompletableFuture déjà terminé public static <U> CompletableFuture<U> completedFuture(U value) ; public static <U> CompletableFuture<U>  supplyAsync(Supplier<U> value, Executor executor) ; public static <U> CompletableFuture<U>  runAsync(Runnable runnable, Executor executor) ;
  37. 37. #J8Async @JosePaumard CompletionStageCompletionStage • Concept : un étape dans un traitement global  Peut être déclenchée par une étape précédente  Peut déclencher d’autres étapes  Peut être exécutée dans un executor particulier
  38. 38. #J8Async @JosePaumard CompletionStageCompletionStage • Notion tâche :  Function : prend un argument, retourne une valeur  Consumer : prend un argument  Runnable = interfaces fonctionnelles, donc lambda
  39. 39. #J8Async @JosePaumard CompletionStage – chaînageCompletionStage – chaînage • Chaînage après, même thread public <U> CompletionStage<U>  thenApply(Function<? super T,? extends U> fn); public CompletionStage<Void>  thenAccept(Consumer<? super T> action); public CompletionStage<Void>  thenRun(Runnable action);
  40. 40. #J8Async @JosePaumard CompletionStage – chaînageCompletionStage – chaînage • Chaînage après, autre thread (common FJ pool) public <U> CompletionStage<U>  thenApplyAsync(Function<? super T,? extends U> fn); public CompletionStage<Void>  thenAcceptAsync(Consumer<? super T> action); public CompletionStage<Void>  thenRunAsync(Runnable action);
  41. 41. #J8Async @JosePaumard CompletionStage – chaînageCompletionStage – chaînage • Chaînage après, autre thread (executor) public <U> CompletionStage<U>  thenApplyAsync(Function<? super T,? extends U> fn, Executor executor); public CompletionStage<Void>  thenAcceptAsync(Consumer<? super T> action, Executor executor); public CompletionStage<Void>  thenRunAsync(Runnable action, Executor executor);
  42. 42. #J8Async @JosePaumard CompletionStage – compositionCompletionStage – composition • Composition public <U> CompletionStage<U>  thenCompose(Function<? super T, ? extends CompletionStage<U>> fn); public CompletionStage<Void>  thenComposeAsync( Function<? super T, ? extends CompletionStage<U>> fn); public CompletionStage<Void>  thenComposeAsync( Function<? super T, ? extends CompletionStage<U>> fn, Executor executor);
  43. 43. #J8Async @JosePaumard Chaînage & compositionChaînage & composition • Ces deux familles de fonction permettent d’enchaîner une opération après l’autre
  44. 44. #J8Async @JosePaumard Chaînage & compositionChaînage & composition • On peut aussi enchaîner une tâche à la suite de deux autres tâches public CompletionStage<V> thenCombine (CompletionStage<U> other,  BiFunction<T, U, V> function) ;
  45. 45. #J8Async @JosePaumard Chaînage & compositionChaînage & composition • On peut aussi enchaîner une tâche à la suite de deux autres tâches • Prend les résultats de this et other  Et les combine dans function public CompletionStage<V> thenCombine (CompletionStage<U> other,  BiFunction<T, U, V> function) ;
  46. 46. #J8Async @JosePaumard Chaînage & compositionChaînage & composition • On peut aussi enchaîner une tâche à la suite de deux autres tâches public CompletionStage<V> thenCombine (CompletionStage<U> other,  BiFunction<T, U, V> function) ; public CompletionStage<V> thenCombineAsync (CompletionStage<U> other,  BiFunction<T, U, V> function) ;
  47. 47. #J8Async @JosePaumard Chaînage & compositionChaînage & composition • On peut aussi enchaîner une tâche à la suite de deux autres tâches public CompletionStage<V> thenCombine (CompletionStage<U> other,  BiFunction<T, U, V> function) ; public CompletionStage<V> thenCombineAsync (CompletionStage<U> other,  BiFunction<T, U, V> function, Executor executor) ;
  48. 48. #J8Async @JosePaumard Chaînage & compositionChaînage & composition • Versions avec Consumer public CompletionStage<Void> thenAcceptBoth (CompletionStage<U> other,  BiConsumer<T, U> action) ; public CompletionStage<Void> thenAcceptBothAsync (CompletionStage<U> other,  BiConsumer<T, U> action) ; public CompletionStage<Void> thenAcceptBothAsync (CompletionStage<U> other,  BiConsumer<T, U> action, Executor executor) ;
  49. 49. #J8Async @JosePaumard Chaînage & compositionChaînage & composition • Versions avec Runnable public CompletionStage<Void> runAfterBoth (CompletionStage<?> other,  Runnable action) ; public CompletionStage<Void> runAfterBothAsync (CompletionStage<?> other,  Runnable action) ; public CompletionStage<Void> runAfterBothAsync (CompletionStage<?> other,  Runnable action, Executor executor) ;
  50. 50. #J8Async @JosePaumard Chaînage & compositionChaînage & composition • Ces tâches se déclenchent conditionnellement à this et à la tâche passée en paramètre • Lorsque ces tâches sont terminées • On peut aussi déclencher lorsque la première se termine
  51. 51. #J8Async @JosePaumard Chaînage multipleChaînage multiple • Version function public CompletionStage<V> applyToEither (CompletionStage<? extends T> other,  Function<T, U> function) ;
  52. 52. #J8Async @JosePaumard Chaînage multipleChaînage multiple • Version function public CompletionStage<U> applyToEither (CompletionStage<? extends T> other,  Function<T, U> function) ; public CompletionStage<U> applyToEitherAsync (CompletionStage<? extends T> other,  Function<T, U> function) ; public CompletionStage<U> applyToEitherAsync (CompletionStage<? extends T> other,  Function<T, U> function, Executor executor) ;
  53. 53. #J8Async @JosePaumard Chaînage multipleChaînage multiple • Version consumer public CompletionStage<V> acceptEither (CompletionStage<? extends T> other,  Consumer<? extends T> consumer) ; public CompletionStage<V> acceptEitherAsync (CompletionStage<? extends T> other,  Consumer<? extends T> consumer) ; public CompletionStage<V> acceptEitherAsync (CompletionStage<? extends T> other,  Consumer<? extends T> consumer, Executor executor) ;
  54. 54. #J8Async @JosePaumard Chaînage multipleChaînage multiple • Version runnable public CompletionStage<V> runAfterEither (CompletionStage<U> other,  Runnable action) ; public CompletionStage<V> runAfterEitherAsync (CompletionStage<U> other,  Runnable action) ; public CompletionStage<V> runAfterEitherAsync (CompletionStage<U> other,  Runnable action, Executor executor) ;
  55. 55. #J8Async @JosePaumard Création sur plusieurs tâchesCréation sur plusieurs tâches • Méthodes statiques public static CompletableFuture<Void>  allOf(CompletableFuture<?>... cfs) ; public static CompletableFuture<Object>  anyOf(CompletableFuture<?>... cfs) ;
  56. 56. #J8Async @JosePaumard Création sur plusieurs tâchesCréation sur plusieurs tâches • Attention à la sémantique ! • Imprime « null » public static CompletableFuture<Void>  allOf(CompletableFuture<?>... cfs) ; CompletableFuture<Void> allOf = CompletableFuture.allOf() ; System.out.println("allOF : " + allOf.join()) ;
  57. 57. #J8Async @JosePaumard Création sur plusieurs tâchesCréation sur plusieurs tâches • Attention à la sémantique ! • Ne rend pas la main… public static CompletableFuture<Void>  allOf(CompletableFuture<?>... cfs) ; CompletableFuture<Object> anyOf = CompletableFuture.anyOf() ; System.out.println("anyOf : " + anyOf.join()) ;
  58. 58. #J8Async @JosePaumard Création sur plusieurs tâchesCréation sur plusieurs tâches • Attention à la sémantique ! public static CompletableFuture<Void>  allOf(CompletableFuture<?>... cfs) ; CompletableFuture<Object> anyOf = CompletableFuture.anyOf() ; System.out.println("anyOf : " + anyOf.getNow("Nothing to say")) ;
  59. 59. #J8Async @JosePaumard Gestion des exceptionsGestion des exceptions • Point délicat :  Une première étape consiste à créer les tâches et à décrire leur enchaînement  L’exécution des tâches démarre indépendamment des appels  À chaque étape, un CompletableFuture est créé
  60. 60. #J8Async @JosePaumard Gestion des exceptionsGestion des exceptions • Un CompletableFuture peut dépendre :  Cas 1 : d’un autre CompletableFuture  Cas 2 : de deux autres CompletableFuture  Cas 3 : de N CompletableFuture
  61. 61. #J8Async @JosePaumard Gestion des exceptionsGestion des exceptions • Une exception est jetée dans le cas 1  Tous les CompletableFuture sont en erreur • Ils se terminent « exceptionnellement »  isExceptionnaly() retourne true  L’appel à get() jette une ExecutionException  get().getCause() retourne l’exception première
  62. 62. #J8Async @JosePaumard Gestion des exceptionsGestion des exceptions • Une exception est jetée dans le cas 2  Tous les CompletableFuture en aval sont en erreur • Ils se terminent « exceptionnellement » • L’autre tâche peut se terminer normalement  On peut l’interroger par get() pour avoir son résultat
  63. 63. #J8Async @JosePaumard Gestion des exceptionsGestion des exceptions • Une exception est jetée dans le cas 3  Le CompletableFuture retourné est en erreur • Il se termine « exceptionnellement » • Les autres tâches peuvent se terminer normalement  On peut l’interroger par get() pour avoir son résultat
  64. 64. #J8Async @JosePaumard Gestion des exceptionsGestion des exceptions • On peut aussi traiter une exception normalement  Dans ce cas, l’exception est passée à la fonction  Utile pour les checked exception CompletionStage<T> exceptionally( Function<Throwable, ? extends T> function);
  65. 65. #J8Async @JosePaumard Gestion des exceptionsGestion des exceptions • Méthode whenComplete()  Dans ce cas t ou e est nul dans l’appel de action  Le CompletableFuture retourné peut ne pas être en erreur CompletionStage<T> whenComplete (BiConsumer<T, Throwable> action) ;
  66. 66. #J8Async @JosePaumard Gestion des exceptionsGestion des exceptions • Méthode whenComplete() CompletionStage<T> whenComplete (BiConsumer<T, Throwable> action) ; CompletionStage<T> whenCompleteAsync (BiConsumer<T, Throwable> action) ; CompletionStage<T> whenCompleteAsync (BiConsumer<T, Throwable> action, Executor executor) ;
  67. 67. #J8Async @JosePaumard Gestion des exceptionsGestion des exceptions • Méthode handle()  Dans ce cas t ou e est nul dans l’appel de function  Retourne un CompletableFuture qui peut ne pas être en erreur CompletionStage<T> handle (BiFunction<T, Throwable, U> function) ;
  68. 68. #J8Async @JosePaumard Gestion des exceptionsGestion des exceptions • Méthode handle() CompletionStage<T> handle (BiFunction<T, Throwable, U> function) ; CompletionStage<T> handleAsync (BiFunction<T, Throwable, U> function) ; CompletionStage<T> handleAsync (BiFunction<T, Throwable, U> function, Executor executor) ;
  69. 69. #J8Async @JosePaumard Une dernière méthodeUne dernière méthode • CompletableFuture : On peut obtenir une estimation du nombre de tâches qui attendent l’exécution d’une tâche donnée int getNumberOfDependents() ;
  70. 70. #J8Async @JosePaumard Exemple – 1Exemple – 1 • Lecture asynchrone de liens et affichage CompletableFuture.supplyAsync( () ‐> readPage("http://whatever.com/") )
  71. 71. #J8Async @JosePaumard Exemple – 1Exemple – 1 • Lecture asynchrone de liens et affichage CompletableFuture.supplyAsync( () ‐> readPage("http://whatever.com/") ) .thenApply(page ‐> linkParser.getLinks(page))
  72. 72. #J8Async @JosePaumard Exemple – 1Exemple – 1 • Lecture asynchrone de liens et affichage CompletableFuture.supplyAsync( () ‐> readPage("http://whatever.com/") ) .thenApply(page ‐> linkParser.getLinks(page)) .thenAccept( links ‐> displayPanel.display(links) ) ;
  73. 73. #J8Async @JosePaumard Exemple – 1Exemple – 1 • Lecture asynchrone de liens et affichage CompletableFuture.supplyAsync( () ‐> readPage("http://whatever.com/") ) .thenApply(page ‐> linkParser.getLinks(page)) .thenAcceptAsync( links ‐> displayPanel.display(links), executor ) ;
  74. 74. #J8Async @JosePaumard Exemple – 1Exemple – 1 • Lecture asynchrone de liens et affichage public interface Executor { void execute(Runnable command); }
  75. 75. #J8Async @JosePaumard Exemple – 1Exemple – 1 • Lecture asynchrone de liens et affichage public interface Executor { void execute(Runnable command); } Executor executor = runnable ‐> SwingUtilities.invokeLater(runnable) ;
  76. 76. #J8Async @JosePaumard Exemple – 1Exemple – 1 • Lecture asynchrone de liens et affichage CompletableFuture.supplyAsync( () ‐> readPage("http://whatever.com/") ) .thenApply(page ‐> linkParser.getLinks(page)) .thenAcceptAsync( links ‐> displayPanel.display(links),  runnable ‐> SwingUtilities.invokeLater(runnable) ) ;
  77. 77. #J8Async @JosePaumard Exemple – 1Exemple – 1 • Lecture asynchrone de liens et affichage CompletableFuture.supplyAsync( () ‐> readPage("http://whatever.com/") ) .thenApply(Parser::getLinks) .thenAcceptAsync( DisplayPanel::display,  SwingUtilities::invokeLater ) ;
  78. 78. #J8Async @JosePaumard Exemple – 2Exemple – 2 • Événements asynchrones dans CDI @Inject Event<String> event ; event.fire("some event") ; // returns void public void observes(@Observes String payload) { // handle the event, called in the firing thread }
  79. 79. #J8Async @JosePaumard Exemple – 2Exemple – 2 • Événements asynchrones dans CDI public void observes(@Observes String payload) { // handle the event, called in the firing thread CompletableFuture.anyOf(/* some task */) ; }
  80. 80. #J8Async @JosePaumard Exemple – 2Exemple – 2 • Événements asynchrones dans CDI @Inject Event<String> event ; event.fireAsync("some event") ; // returns CompletableFuture<Object> (?) public void observes(@Observes String payload) { // handle the event in another thread }
  81. 81. #J8Async @JosePaumard Exemple – 2Exemple – 2 • Événements asynchrones dans CDI @Inject Event<String> event ; event.fireAsync("some event", executor) ;  public void observes(@Observes String payload) { // handle the event in the executor }
  82. 82. #J8Async @JosePaumard Exemple – 2Exemple – 2 • Événements asynchrones dans CDI @Inject Event<String> event ; event.fireAsync("some event", executor) ;  @Produces @SwingExecutor Executor executor = SwingUtilities::invokeLater public void observes(@Observes String payload,  @SwingExecutor Executor executor) { // handle the event in the Swing thread }
  83. 83. #J8Async @JosePaumard Exemple – 2Exemple – 2 • Événements asynchrones dans CDI @Inject Event<String> event ; event.fireAsync("some event", executor) ;  @Produces @SwingExecutor Executor executor = SwingUtilities::invokeLater public void observes(@Observes @SwingExecutor String payload) { // handle the event in the Swing thread }
  84. 84. #J8Async @JosePaumard Exemple – 3Exemple – 3 CompletableFuture<String> closing = new CompletableFuture<String>() ; Stream<String> manyStrings = Stream.of("one", "two", "three") ; CompletableFuture<String> reduce = manyStrings .onClose(() ‐> { closing.complete("Closed") ; }) .map(CompletableFuture::completedFuture) .reduce( closing,   (cf1, cf2) ‐> cf1.thenCombine(cf2, function) // concatenation ) ; manyStrings.close() ;
  85. 85. #J8Async @JosePaumard L’indispensable !L’indispensable ! • Fixer la taille du Common Fork / Join Pool System.setProperty( "java.util.concurrent.ForkJoinPool.common.parallelism", 2) ;
  86. 86. #J8Async @JosePaumard ConclusionConclusion • On a une API pour le calcul asynchrone dans le JDK !
  87. 87. #J8Async @JosePaumard ConclusionConclusion • On a une API pour le calcul asynchrone dans le JDK ! • Très riche et souple à l’utilisation
  88. 88. #J8Async @JosePaumard ConclusionConclusion • On a une API pour le calcul asynchrone dans le JDK ! • Très riche et souple à l’utilisation • Construite sur l’utilisation des lambda
  89. 89. #J8Async @JosePaumard ConclusionConclusion • On a une API pour le calcul asynchrone dans le JDK ! • Très riche et souple à l’utilisation • Construite sur l’utilisation des lambda • Permet un contrôle fin des threads
  90. 90. #J8Async @JosePaumard ConclusionConclusion • On a une API pour le calcul asynchrone dans le JDK ! • Très riche et souple à l’utilisation • Construite sur l’utilisation des lambda • Permet un contrôle fin des threads • Gère différents types de chaînage
  91. 91. #J8Async @JosePaumard ConclusionConclusion • On a une API pour le calcul asynchrone dans le JDK ! • Très riche et souple à l’utilisation • Construite sur l’utilisation des lambda • Permet un contrôle fin des threads • Gère différents types de chaînage • Gère intelligemment les exceptions
  92. 92. @JosePaumard#J8Stream
  93. 93. @JosePaumard#J8Stream

×