Les nouveautés de java 9 pour les développeurs
Maintenant que Java 9 est Features Complete, il est temps de se pencher sur toutes les nouveautés que cette nouvelle version va nous apporter, à nous, dévelopeurs utilisant java.
Bien sur, tout le monde a entendu parler de la modularisation du JDK (project Jigsaw) attendu depuis très longtemps. Et bien, je ne vais pas en parler ici! Je vais m’attacher aux nouveautés de Java 9 qui sont à destination des dévelopeurs lambda, pas celles à destination des auteurs de framework ou des utilisateurs avancés.
Tout d’abord, le dévelopement de Java 9 a été principalement réalisé via un ensemble de JEP (Java Enhancement Proposal, plus d’info ici : http://openjdk.java.net/jeps/1) dont on peut trouver la liste ici : http://openjdk.java.net/projects/jdk9/. De toutes ces JEPs, je vais parcourir celles qui contiennent des changements visibles et utilisables dans la vie de tout les jours des développeurs.
JEP 102: Process API Updates
Gerer depuis java l’appel a un programme (process) externe a toujours été compliqué. Surtout si on veut faire des opérations qui semblent basique mais qui n’ont pas été prévue dans l’implémentation actuelle (récupérer le PID, killer un process, récupérer la ligne de commande, …).
Via le JEP 102 : Process API Updates, l’API Process a été grandement améliorée pour permettre toutes ces opérations, sur le process de la JVM (ProcessHandle.current()), ou un process fils créer via Runtime.getRuntime().exec(« your_cmd »). De plus, comme toujours avec java, c’est compatible avec les différents OS supportés par Java (et donc avec Windows ET Linux !).
En voici quelques exemples d’utilisations en Java 9:
// Get PIDs of own processes System.out.println("Your pid is " + ProcessHandle.current().getPid()); //start a new process and get PID Process p = Runtime.getRuntime().exec("sleep 1h"); ProcessHandle h = ProcessHandle.of(p.getPid()).orElseThrow(IllegalStateException::new); // Do things on exiting process : CompletableFuture ! h.onExit().thenRun( () -> System.out.println("Sleeper exited") ); // Get info on process : return Optional! System.out.printf("[%d] %s - %s\n", h.getPid(), h.info().user().orElse("unknown"), h.info().commandLine().orElse("none")); // Kill a process h.destroy();
JEP 110: HTTP/2 Client (Incubator)
HTTP/2 est là, et beaucoup de site l’exploitent déjà. Il était temps que Java en propose une implémentation cliente. Au passage, l’API a été revue pour proposer une implémentation plus simple à utiliser que la très ancienne HttpURLConnection qui jusqu’ici été la seule manière de faire des appels HTTP en Java dans le JDK (et la raison pour laquelle les dévelopeurs utilisaient des librairies tierces tels que Apache HttpClient ou OKHttp). Avec la JEP 110: HTTP/2 Client (Incubator) Java se dote d’un client HTTP au goût du jour, synchrone ou asynchrone (il est alors basé sur les CompletableFuture).
En voici un exemple en utilisation asynchrone :
/** * The HTTP API functions asynchronously and synchronously. In asynchronous mode, work is done in threads (ExecutorService). */ public static void main(String[] args) throws Exception { HttpClient.getDefault() .request(URI.create("http://www.loicmathieu.fr")) .GET() .responseAsync() // CompletableFuture .thenAccept(httpResponse -> System.out.println(httpResponse.body(HttpResponse.asString())) ); Thread.sleep(999); // Give worker thread some time. }
JEP 259: Stack-Walking API
Parcourir une stack d’execution en Java n’a jamais été facile … c’est désormais du passé, grâce à la Stack-Walking API : http://openjdk.java.net/jeps/259
// return class/method only for our classes. private static List<String> walkAndFilterStackframe() { return StackWalker.getInstance().walk(s -> s.map( frame -> frame.getClassName() + "/" + frame.getMethodName()) .filter(name -> name.startsWith("fr.loicmathieu")) .limit(10) .collect(Collectors.toList()) ); }
JEP 269: Convenience Factory Methods for Collections
La JEP 269 apporte un des plus grand changement en terme d’API : des méthodes statiques sur les interfaces de l’API Collection pour la création facilitée de collection immuable. Plus d’info ici : http://openjdk.java.net/jeps/269. La cible est la création de petite collections (list, set, map). La performance et l’utilisation mémoire ont été au centre de l’implémentation de ces collections.
List<Integer> listOfNumbers = List.of(1, 2, 3, 4, 5); Set<Integer> setOfNumbers = Set.of(1, 2, 3, 4, 5); Map<String, String> mapOfString = Map.of("key1", "value1", "key2", "value2"); Map<String, String> moreMapOfString = Map.ofEntries( Map.entry("key1", "value1"), Map.entry("key2", "value2"), Map.entry("key1", "value3") );
JEP 266: More Concurrency Updates
La JEP 266: More Concurrency Updates comme son nom ne l’indique pas, contient une des évolutions majeur de Java 9 : une implémentation des Reactive Stream en Java via la classe Flow. Java va alors rejoindre le groupe très hype des languages réactifs!
La classe Flow propose donc trois interfaces pour implémenter vos stream réactives :
Publisher :
Produit des messages que les subscriber vont consomer. La seule methode estsubscribe(Subscriber).
Subscriber :
Souscrit a un publisher pour recevoir des messages (via la methodeonNext(T)
), des messages d’erreur (onError(Throwable)
), ou un signal comme quoi il n’y aura plus de messages (onComplete()
). Avant toute chose, le publisher doit appeleronSubscription(Subscription)
.Subscription :
La connexion entre un publisher et un subscriber. Le subscriber va l’utiliser pour demander des messages (request(long)
) ou pour rompre la connexion (cancel()
).
Voici un exemple tiré du tutoriel disponible ici :
public class MySubscriber<T> implements Subscriber<T> { private Subscription subscription; @Override public void onSubscribe(Subscription subscription) { this.subscription = subscription; subscription.request(1); //a value of Long.MAX_VALUE may be considered as effectively unbounded } @Override public void onNext(T item) { System.out.println("Got : " + item); subscription.request(1); //a value of Long.MAX_VALUE may be considered as effectively unbounded } @Override public void onError(Throwable t) { t.printStackTrace(); } @Override public void onComplete() { System.out.println("Done"); } }
Et voici un exemple de Publisher
//Create Publisher SubmissionPublisher<String> publisher = new SubmissionPublisher<>(); //Register Subscriber MySubscriber<String> subscriber = new MySubscriber<>(); publisher.subscribe(subscriber); //Publish items System.out.println("Publishing Items..."); String[] items = {"1", "x", "2", "x", "3", "x"}; Arrays.asList(items).stream().forEach(i -> publisher.submit(i)); publisher.close();
De plus, quelques changement à l’API CompletableFuture (sorte d’équivalent des Promise JavaScript en Java) ont vu le jour permettant, entre autre, une meilleur composition des CompletableFuture entre elle :
- copy():CompletableFuture
- completeAsync(Supplier< ? extends T> supplier):CompletableFuture
- orTimeout(long timeout,TimeUnit unit):CompletableFuture
- completeOnTimeout(T value, long timeout, TimeUnit unit):CompletableFuture
- failedFuture(Throwable ex):CompletableFuture
Pour la liste exaustive des nouvelles méthodes, voir les méthodes « Since 9 » de la javadoc : http://download.java.net/java/jdk9/docs/api/java/util/concurrent/CompletableFuture.html
JEP 277: Enhanced Deprecation
La JEP 277: Enhanced Deprecation permet de préciser via l’annotation @Deprecated deux attributs essentiels qui permettent aux développeurs d’avoir connaissance de si l’API utilisée est destinée à être supprimée et quand la deprecation a été mise. Ceci dans le but de faciliter le cycle de vie des applications et de permettre, peut-être plus sereinement, de supprimer dans le futur certaines API du JDK lui-même.
Voici un petit exemple qui précise que MyDeprecatedClass est déprécié depuis la version 10 et sera supprimé un jour :
@Deprecated(since="9", forRemoval=true) public class MyDeprecatedClass { //deprecated stuff }
JEP 222: jshell: The Java Shell (Read-Eval-Print Loop)
Beaucoup de languages (ruby, scala, python, …) proposent une Read-Evaluate-Print-Loop (REPL), un shell. Celà permet un apprentissage aisé du language et donne une porte d’entré direct depuis un simple shell. Que ça soit pour introduire le language, prototyper ou tester c’est toujours un plus d’avoir un shell et d’éviter le cérémonial d’édition, de compilation, de packaging du code.
Avec Java 9, apparait jshell, le REPL de java ! Plus d’info dans la JEP : http://openjdk.java.net/jeps/222
Depuis une ligne de commande, exécutez
Un article qui rentre plus en détail : http://jakubdziworski.github.io/java/2016/07/31/jshell-getting-started-examples.html
JEP 213: Milling Project Coin
Project Coin est un projet d’évolution du language, qui a débuté avec java 7 dans le but de simplifier l’utilisation du language pour les développeurs en apportant des petites modifications de l’ordre du « sucre syntaxique ». La JEP213 : Milling Project Coin contient l’implémentation dans Java 9 des dernières partie du projet.
- @SafeVarargs autorisé sur une méthode privé (précédemment uniquement dans les méthodes static ou finale)
- Autorisation de l’opérateur <> pour les classes abstraite (quand le type est dénotable)
- Suppression de ‘_’ comme un identificateur valide pour permettre sa réutilisation en Java 10 (traditionnelement utilisé pour nommer un attribut, en cas de surchage de méthode, qu’on ne veut pas utiliser)
- Méthode privé dans les interfaces : permet la factorisation de code de méthode statique (factorisation de code entre deux méthodes statiques de la même interface)
- Autorisation des variables finale (ou effectivement finale) dans un try-with-resource (exemple ci-dessous) :
Avant Java 9:
final Resource r = new Resource(); try (Resource r2 = r) { … }
En Java 9 :
final Resource r = new Resource(); try (r) { … // Cannot mutate r }
JEP 211 : Elide Deprecation Warnings on Import Statements
Avant : importer une classe dépréciée générait un warning à la compilation, après … plus de warning. Plus d’info ici : http://openjdk.java.net/jeps/211
Pleins d’autres changements hors JEP :
Stream & Collectors:
- Stream.takeWhile(Predicate< ? super T> predicate):Stream
: construit une stream qui contient les éléments de la première tant que le prédicat est vrai. Dès que le prédicat devient faux la stream est coupée. - Stream.dropWhile(Predicate< ? super T> predicate):Stream
: l’inverse de takeWhile, construit une stream qui contient le premier élément faux puis les suivants. Tant que le prédicat est faux : supprime les émélments, dès qu’il est vrai, inclue les éléments dans la stream. - Stream.ofNullable(T element):Stream
: retourne une stream avec l’élément ou une stream vide si l’élément est nulle. Evite l’utilisation d’Optional avec les stream - Stream.iterate(T, Predicate< ? super T>, UnaryOperator
) : réplique une boucle for standard : Stream.iterate(0; i -> i<10, i -> i+1) - Collectors.filtering(Predicate< ? super T>,Collector< ? super T,A,R>) : execute un filtre en amont du collector (voir un exemple dans la Javadoc qui explique la différence avec l’utilisation de Stream.filter() avant l’utilisation d’un collector)
- Collectors.flatMapping(Function< ? super T,? extends Stream extends U>>,Collector< ? super U,A,R>) : execute une operation de type flatMap en amont du collector (voir un exemple concret dans la Javadoc)
//iterate /java 8 style : using for loop for (int i = 0; i < 10; ++i) { System.out.println(i); } //java 9 style, using Stream.iterate Stream.iterate(0, i -> i < 10, i -> i + 1).forEach(System.out::println); //takeWhile and dropWhile Stream<String> stream = Stream.iterate("", s -> s + "s") stream.takeWhile(s -> s.length() < 10); stream.dropWhile(s -> !s.contains("sssss")); //ofNullable : returning Stream.empty() for null element //java 8 style : we need to make a check to know if it's null or not collection.stream() .flatMap(s -> { Integer temp = map.get(s); return temp != null ? Stream.of(temp) : Stream.empty(); }) .collect(Collectors.toList()); //java 9 style collection.stream().flatMap(s -> Stream.ofNullable(map.get(s))).collect(Collectors.toList());
List<Integer> numbers = List.of(1, 2, 3, 5, 5); Map<Integer, Long> result = numbers.stream() .filter(val -> val > 3) .collect(Collectors.groupingBy(i ->; i, Collectors.counting())); result = numbers.stream() .collect(Collectors.groupingBy(i -> i, Collectors.filtering(val -> val > 3, Collectors.counting()) ));
Un article sur le sujet : http://www.baeldung.com/java-9-stream-api
Optional
4 nouvelles méthodes ont été ajoutée à la classe Optional :
- or(Supplier):Optional : retourne le même Optional ou un Optional construit avec le Supplier passé en paramètre si il n’y a pas de valeur. Cela permet une construction lazy d’un autre Optional si la valeur du premier n’existe pas.
- ifPresent(Consumer):void : execute le Consumer en paramètre s’il y a une valeur de présente
- ifPresentOrElse(Consumer, Runnable):void : execute le Consumer en paramètre s’il y a une valeur de présente sinon execute le Runnable
- stream():Stream
: retourne un Stream d’un element s’il y a une valeur ou un Stream vide. Cela permet l’utilisation de l’API Stream avec les Optional.
//Optional.or : a lazy version of orElse Optional<String> value = ...; Optional<String> defaultValue = Optional.of(() -> bigComputation()); return value.or(defaultValue);//bigComputation will be called only if value is empty //Optional.ifPresent : Optional<String> value = ...; AtomicInteger successCounter = new AtomicInteger(0); value.ifPresent( v -> successCounter.incrementAndGet()); //Optional.ifPresentOrElse : Optional<String> value = ...; AtomicInteger successCounter = new AtomicInteger(0); AtomicInteger onEmptyOptionalCounter = new AtomicInteger(0); value.ifPresentOrElse( v -> successCounter.incrementAndGet(), onEmptyOptionalCounter::incrementAndGet); //Optional.stream : unify the stream and Optional API Optional<String> value = Optional.of("a"); List<String> collect = value.stream().map(String::toUpperCase).collect(Collectors.toList()); //["A"]
Un article sur le sujet : http://www.baeldung.com/java-9-optional
Java Time :
Plusieurs ajouts à l’API Java Time, entre autre la possibilité de créer un Stream de date avec LocalDate.datesUntil(LocalDate) and LocalDate.datesUntil(LocalDate,Period) .
Plus d’info ici : http://blog.joda.org/2017/02/java-time-jsr-310-enhancements-java-9.html
Autre :
- InputStream.readAllBytes():byte[] : lit d’un seul coup un input stream en tableau de byte
- InputStream.readNBytes(byte[] b, int off, int len):int : lit d’un seul coup un input stream dans le tableau de byte en paramètre avec offset et limite.
- Objects.requireNonNullElse(T obj, T defaultObj) : retourne le premier éléments si non null, sinon le deuxième. Si les deux sont null : NullPointerException !
- Objects.requireNonNullElseGet(T obj, Supplier supplier) : retourne le premier éléments si non null, sinon appel le supplier.get().
- int Objects.checkIndex(int index, int length) : check de l’index : génère un IndexOutOfBoundsException si l’index est inférieur à 0 ou supérieur ou éagle à la taille. Cette méthode peut-être optimisée par le JVM.
- int Objects.checkFromToIndex(int fromIndex, int toIndex, int length) : idem mais pour le sous-range fromIndex/toIndex
- int Objects.checkFromIndexSize(int fromIndex, int size, int length) : idem mais pour le sous-range fromIndex/fromIndex + size
- {Math, StrictMath}.fma() : implémentation de l’opération fused-multiply-accumulate (fma)
- {Math, StrictMath}.{multiplyExact, floorDiv, floorMod}
- {BigDecimal, BigInteger}. sqrt() : racine carré
- Arrays.equals(), Arrays.compare(), Arrays.compareUnsigned(), Arrays.mismatch() : beaucoup de nouvelles méthodes permettant de comparer des tableaux dans plein de variantes : signée, non signées, avec from/to index, égalité, différence, …
Performance :
Les JEP suivantes sont axès sur la performance de la JVM, je ne les présenterais pas en détail ici (mais certainement dans un prochain article) mais en voici la liste :
- JEP 143: Improve Contended Locking : optimisation des monitors Java (optimisation des lock).
- JEP 193: Variable Handles : C++ atomics …
- JEP 197: Segmented Code Cache : le code cache (une partie du Metaspace) a été séparé pour en optimiser les performances.
- JEP 254: Compact Strings : revue de l’implémentation des String en Java pour en proposer une version plus compact en cas de Sring ISO-8859-1 (ou Latin-1). Les String Java sont stockée en UTF-16 par défaut : chaque caractère est alors stocké sur deux octets. A la création de la String, si elle est comptabile ISO-8859-1 elle sera stocké de manière compacte avec un caractère sur un byte.
- JEP 274: Enhanced Method Handles : plusieurs ajout à l’API MethodHandle
- JEP 280: Indify String Concatenation : intrasification de la concaténation des chaînes de caractère dans le JVM. Plusieurs startégies de concaténation de chaînes ont été codées dont une à base de Method Handles (avec StringBuilder ou en inline), c’est celle par défaut dorénavant.
- JEP 285: Spin-Wait Hints : pour les utilisateurs bas niveau uniquement : possibilité de préciser à la JVM qu’on est dans une spin-wait-loop …
Liens :
Voici les articles qui m’ont inspirés pour la rédaction de celui-ci :
https://blogs.oracle.com/darcy/resource/Devoxx/DevoxxUS-2017-jdk9-lang-tools-libs.pdf
http://docs.oracle.com/javase/9/whatsnew/toc.htm#JSNEW-GUID-BA9D8AF6-E706-4327-8909-F6747B8F35C5
http://blog.takipi.com/5-features-in-java-9-that-will-change-how-you-develop-software-and-2-that-wont/
https://bentolor.github.io/java9-in-action
http://www.javaworld.com/article/2598480/core-java/why-developers-should-get-excited-about-java-9.html
https://www.sitepoint.com/ultimate-guide-to-java-9/
http://www.javamagazine.mozaicreader.com/JulyAug2017
One thought on “Les nouveautés de java 9 pour les développeurs”
Article très intéressant, j’ai vu pas mal de choses que je n’avais pas vu ailleurs dans d’autres articles « What’s new in Java 9… »
Pour infos mes features préférées sont :
* Factory Methods for Collections (hier encore je galérais à initialiser une Map statique et immutable…)
* Les nouvelles méthodes de stream (surtout takeWhile et dropWhile, mais les autres aussi)
* ifPresentOrElse de l’Optional
* InputStream.readAllBytes[], que je ne connaissais pas, tellement pratique !
J’ai pu tetser un peu Jshell, je n’étais pas convaincu, je trouve que ce n’était pas le même feeling qu’en Scala ou en Clojure. Par contre, ça peut être pratique pour tester un truc du langage dont on est pas certain.
J’ai hâte de voir venir le pattern matching et les collections immutables quand même. Après, il ne manquera plus que le syntaxic sugar pour initialiser les maps et ça sera parfait !
Petit tips pour finir :
Tu peux transformer ‘Arrays.asList(items).stream()’ en ‘Arrays.stream(items)’ si items est un tableau. Ca évite le passage par une collection supplémentaire.
Alex