Les nouveautés de Java 9, 10 et 11 

Pour les développeurs

Loïc Mathieu -        @loicmathieu

Gouvernance

Cycle de vie

Jusqu'à Java 9 : fonctionnement en features release : 

  • Une liste de fonctionnalités était définie pour une release
  • La date de release était planifiée : généralement une toute les 2 ans
  • Dès qu'une fonctionnalités prenait plus de temps : la release était décalée

 

Ceci à mener à :

  • Des releases de tailles importantes difficile à sortir
  • Des releases tout les 3 ans plutôt que 2
  • Des nouvelles version compliqués à prendre en main par les développeurs

Cycle de vie

Depuis Java 10 : fonctionnement en release train

  • On définit un rythme pour les releases
  • Dès qu'une fonctionalité est prête, on l'intègre dans la future release
  • On release à date ce qui est intégré.

 

Ceci à mener à :

  • Des releases tous les 6 mois
  • Des releases plus petites et donc facile à livrer et à intégrer pour les développeurs
  • Un développement plus facile et rapide de fonctionnalités dans Java

Support

Depuis Java 10 on a donc une release tous les 6 mois.

 

Toutes les 6 releases on va avoir une release Long Term Support -- LTS.

 

La politique de support gratuit a changé :

  • Une release non-LTS sera supportée 6 mois
  • Une release LTS sera supportée au minimum 3 ans
  • Java 8 est EOL depuis mi-janvier 2019
  • Oracle fournit du support étendu payant

 

Pas de panique, d'autres proposent du support étendu gratuit :)

LTS ou Latest ?

Source : Java Developer Survey 2018 (10500 répondants)

Source: NodeJS user survey 2018 (1650 répondants)

Distribution

OpenJDK est le projet open source d'implémentation du language Java et de la JVM.

 

Beaucoup d'organisations participent à ce projet.

 

Oracle est une de ces organisations (la principale).

 

Depuis Java 11, Oracle fournit deux distributions d'OpenJDK qui sont identiques en terme de fonctionnalités :

  • OpenJDK : support limité dans le temps
  • OracleJDK : support étendu payant

Distribution

Il existe beaucoup d'autres distributions de Java, dont certaines proposent déjà un support plus étendu qu'OpenJDK :

  • *Azul : Zing (payant) et Zulu (gratuit avec support payant)
  • AdoptOpenJDK
  • *RedHat
  • Eclipse via OpenJ9 (implémentation IBM)
  • *Amazon Corretto

 

Source: Java Developer Survey 2018

Java 9

JPMS : The module system

Java a été modularisé : un système de module a été créé et le JDK lui-même a été découpé en plusieurs modules.

 

Chaque module définit les services qu'il expose et les modules qu'il utilise : ceci permet d'avoir une vue clair des dépendances des différents modules entre-eux.

 

Les API internes sont dorénavant dans des modules internal non accessible.

 

On peut créer une image native de notre application contenant uniquement les modules nécessaire via jlink.

 

JEP 102: Process API Updates

  • But : faciliter l'appel à un programme (process) externe depuis Java
     
  • Ajout de nombreuses fonctionnalités à l'API actuelle :
    • récupérer le PID,
    • killer un process
    • récupérer la ligne de commande

    •  
  • Ajout de ProcessHandle qui permet de gérer le process de la JVM (ProcessHandle.current()) ou un process fils (Runtime.getRuntime().exec(""))
     
  • Compatible avec les différents OS supportés par Java 

JEP 102: Process API Updates

// 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à.
     
  • Une nouvelle API a été créée pour proposer une implémentation plus simple à utiliser que la très ancienne HttpURLConnection 
     
  • Nouveau client HTTP au goût du jour, synchrone ou asynchrone (alors basé sur les CompletableFuture).
     
  • Disponible dans le module "incubator" dans Java 9
     
  • Ré-écrit dans Java 11, on en reparlera ...

 

 

JEP 269: Convenience Factory Methods for Collections

  • L'un des plus grand changement en terme d’API : des méthodes statiques  pour la création de collection immuable.
     
  • Cible : 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.

JEP 269: Convenience Factory Methods for 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: Flow (Java Reactive Stream)

Implémentation des Reactive Stream en Java via la classe Flow :

  • Publisher : Produit des messages que les subscriber vont consommer. La seule méthode est subscribe(Subscriber).
  • Subscriber : Souscrit a un publisher pour recevoir des messages (via la methode onNext(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 appeler onSubscription(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()).

JEP 266: Flow (Java Reactive Stream)

public class MySubscriber<T> implements Subscriber<T> {  
  private Subscription subscription;  
   
  @Override 
  public void onSubscribe(Subscription subscription) {  
    this.subscription = subscription;  
    subscription.request(1); 
  }  
   
  @Override 
  public void onNext(T item) {  
    System.out.println("Got : " + item);  
    subscription.request(1); 
  }  
   
  @Override 
  public void onError(Throwable t) {  
    t.printStackTrace();  
  }  
   
  @Override 
  public void onComplete() {  
    System.out.println("Done");  
  }  
}

JEP 266: Flow (Java Reactive Stream)

//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();  

JEP 266: CompletableFuture

Quelques changements à l’API CompletableFuture ont vu le jour permettant, entre autre, une meilleur composition des CompletableFuture entre elles :

  • copy():CompletableFuture<T>
  • completeAsync​(Supplier<? extends T> supplier):CompletableFuture<T>
  • orTimeout​(long timeout,TimeUnit unit):CompletableFuture<T>
  • completeOnTimeout​(T value, long timeout, TimeUnit unit):CompletableFuture<T>
  • failedFuture​(Throwable ex):CompletableFuture<T>

JEP 277: Enhanced Deprecation

Ajout de deux attributs à l'annotation @Deprecated :

  • since  : depuis quelle version l'API est dépréciée.
  • forRemoval : mettre à true (par défaut false) si l'API sera supprimée dans une future release.

 

Le but est 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.

@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.

Eviter le cérémonial d’édition, de compilation, de packaging du code.

Avec Java 9, apparait jshell, le REPL de java ! 

Depuis une ligne de commande, exécutez <java_home>/bin/jshell et laissez vous guider par l’aide (/help).

JEP 222: jshell: The Java Shell (Read-Eval-Print Loop)

JEP 213: Syntaxic Sugar (coin)

 

  • @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 dans une future version.
     
  • Méthode privé dans les interfaces : permet la factorisation de code de méthode statique dans une même interface.

JEP 213: Syntaxic Sugar (coin)

Avant Java 9 :

final Resource r = new Resource(); 
try (Resource r2 = r) { … }

Après Java 9 :

final Resource r = new Resource(); 
try (r) { … // Cannot mutate r }
  • Autorisation des variables finale (ou effectivement finale) dans un try-with-resource (exemple ci-dessous) :

Stream

  • Stream.takeWhile(Predicate<? super T> predicate):Stream<T> : 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<T> : 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<T> : 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<T>) : réplique une boucle for standard : Stream.iterate(0; i -> i<10, i -> i+1)

Stream

//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());

Collectors

  • 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)

Collectors

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())
    ));

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<T> : 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

//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"]

Java Time

Plusieurs ajouts à l’API Java Time, entre autre la possibilité de créer un Stream de date avec :

  • LocalDate.datesUntil(LocalDate) 
  • LocalDate.datesUntil(LocalDate,Period) 

InputStream

  • 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

  • Objects.requireNonNullElse(T obj, T defaultObj) 
  • Objects.requireNonNullElseGet(T obj, Supplier supplier) 
  • int Objects.checkIndex(int index, int length)
  • int Objects.checkFromToIndex(int fromIndex,
    int toIndex, int length)
     
  • int Objects.checkFromIndexSize(int fromIndex,
    int size, int length)

Les méthodes check[From]Index* 'peuvent' être optimisée par la JVM !

Arrays

Beaucoup de nouvelles méthodes permettant de comparer des tableaux :

  • Arrays.equals()  
  • Arrays.compare() 
  • Arrays.compareUnsigned()
  • Arrays.mismatch()

Performance 

  • 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).
    • UTF16 : chaque caractère est stocké sur deux octets.
    • ISO-8859-1 chaque caractère est stocké sur un octets.
       
  • JEP 280: Indify String Concatenation : intrasification de la concaténation des chaînes de caractère dans le JVM. 

Java 10

JEP 286 : Local-Variable Type Inference

C’est LA grande nouveauté de Java 10 : la possibilité d’utiliser le mot clé var à la place d’une déclaration standard de variable, à l’intérieur d’une méthode (variable locale) quand le type peut être inféré par le compilateur.

 

Var peut être aussi utilisé dans la déclaration des boucles for et du try-with-resource.

JEP 286 : Local-Variable Type Inference

En Java 9 :

 

 

En Java 10 avec le mot clé var :

 
MyComplexType obj = new MyComplexType();
Map<String,List<MyComplexType>> map = new HashMap<String,List<MyComplexType>>();
var obj = new MyComplexType();
var map = new HashMap<String,List<MyComplexType>>();

Possible aussi dans les boucles et le try-with-resource

Collection Copy factory methods

L’API Collection a été enrichie de méthodes statiques permettant la copie de collection existantes :

  • List.copyOf()
  • Set.copyOf()
  • Map.copyOf()

 

Dans les trois cas la collection retournée sera une collection immuable.

Collectors to unmodifiable List

Dans la même mouvance que le précédent changement, les collectors de l’API Stream ont été enrichit pour permettre la création de collections immuable :

  • Collectors.toUnmodifiableList()
  • Collectors.toUnmodifiableSet()
  • Collectors.toUnmodifiableMap(keyFunc, valueFunc)
  • Collectors.toUnmodifiableMap(keyFunc, valueFunc, mergeFunc)

Optional.orElseThrow()

Optional.orElseThrow() est maintenant la manière privilégiée (au détriment de Optional.get()) pour récupérer un élément dans un Optional.

 

Son nom fait clairement référence au fait que si il n’y a pas d’élément, une exception sera lancée (ce qui est le cas pour Optional.get() mais souvent oublié par les développeurs l’utilisant).

Reader.transferTo(Writer)

Un nouvelle méthode à la classe Reader de directement transférer tout les caractère d’un Reader à un Writer :

 
Reader reader = new FileReader("from.txt");
Writer writer = new FileWriter("to.txt");
reader.transferTo(writer);

Performance

Parallel Full GC for G1

 

G1 est depuis Java 9  l’algorithme de Garbage Collection par défaut. Hors dans celui-ci, quand le collector n’arrive plus à nettoyer assez rapidement la heap de manière concurrente, il déclenche un Full GC qui était mono-threadé.

 

Depuis Java 10 l’implémentation de celui-ci a été changé en multi-threadé, le nombre de threads étant définit par -XX:ParallelGCThreads

Java 11

JEP 323 : Local-Variable Syntax for Lambda Parameters

Permet l’utilisation du mot clé var dans les signatures des lambdas.

 

But : permettre d'ajouter des modificateurs (ex: annotations) sans nécessité de mettre le type.

//avant Java 11
(@NotNull Foo<String> f, @Nullable Bar<String> b) -> s.process(b);

//après Java 11
(@NotNull var f, @Nullable var b) -> s.process(b);

JEP 321 : HTTP Client (Standard) 


//init a client
HttpClient httpClient = HttpClient.newBuilder()
               .version(Version.HTTP_2)  // this is the default
               .build();

//create a request
HttpRequest request = HttpRequest.newBuilder()
               .uri(URI.create("https://http2.github.io/"))
               .GET()   // this is the default
               .build();

//send the request and access the response
HttpResponse<String> response = httpClient.send(request, BodyHandlers.ofString());
logger.info("Response status code: " + response.statusCode());
logger.info("Response headers: " + response.headers());
logger.info("Response body: " + response.body());


//send asynchronously : return a ComparableFuture
httpClient.sendAsync(request, BodyHandlers.ofString())
          .thenAccept(response -> {
       logger.info("Response status code: " + response.statusCode());
       logger.info("Response headers: " + response.headers());
       logger.info("Response body: " + response.body());
});

JEP 330 : Launch Single-File Source-Code Programs 

  • La commande java permet directement de lancer un fichier .java sans passer par javac (compilation)
     
  • La compilation se fait alors en mémoire
     
  • On peut même, sous unix, faire des scripts java avec l'utilisation du shebang : #!/usr/bin/java
     
  • But : réduire la cérémonie du language

String

  • String::repeat
  • String::strip
  • String::stripLeading
  • String::stripTrailing
  • String::isBlank
  • String::lines

Divers

  • CharSequence::compareTo()
  • StringBuilder::compareTo()
  • StringBuffer::compareTo()
  • Reader::nullReader() et Writer::nullWriter()
  • InputStream::readNBytes(int len)
  • Pattern::asMatchPredicate 
  • Optional::isEmpty() 
  • Files::readString() et Files::writeString()
  • Predicate::not() 
  • TimeUnit::convert(Duration)
  • Collection::toArray(). 

JEP 318 : Epsilon GC

A No-Op Garbage Collector ! 

 

  • Développé par Aleksey Shipilev en quelques heures 
  • Ne gère pas la mémoire : alloue uniquement
  • Quand il n'y a plus de mémoire : crash !!!
  • ... Pour les gens désirant travailler sur le domaine du GC

JEP 333 : ZGC

A Scalable Low-Latency Garbage Collector

 

  • Développé par Oracle labs
  • Performances très élevées avec des heap de très grandes tailles  :10ms max de pause avec des heap pouvant aller jusqu’au To)
  • Sacrifie CPU et mémoire pour avoir des pauses basse
  • Expérimental
  • Cible les heaps de grandes tailles (et les machines à beaucoup de CPU?)

Performance - G1

Beaucoup d’optimisation réalisé au sein de G1

 

Selon l’auteur :  en passant de Java 8 à Java 11, on aurait
 

Des pauses 60% plus basse « gratuitement » pour une utilisation mémoire grandement réduite.

Démarrage

Beaucoup de travail a été fait pour optimiser le démarrage de Java en terme de temps de démarrage de la JVM et d'empreinte mémoire mené par Claes Redestad (Oracle).

 

L'implémentation de la modularisation et le passage au G1 GC a impacté le temps de démarrage d'une application, les optimisations réalisé dans les version 9, 10 et 11 permettent donc de contrebalancer ça.

 

Une JVM démarre en 80ms.

 

AppCDS ou jlink permettent de démarrer en quelques ms!

Démarrage

What's next ?

Java 12 : New Switch (JEP325)

//new switch synthax : inspired by lambda
switch (day) {
    case SATURDAY, SUNDAY -> {
        System.out.println("Weekend !!!");
        System.out.println("This is awesome ;)");
        break;
    }
    default -> {
        System.out.println("Default");
        System.out.println("OK OK");
        break;
    }
}

//new switch synthax : implicit break and no default as all cases are covered
switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
    case TUESDAY                -> System.out.println(7);
    case THURSDAY, SATURDAY     -> System.out.println(8);
    case WEDNESDAY              -> System.out.println(9);
}

Java 12 : Switch Expression (JEP325)

//  switch expression using the new synthax : it returns a value !
int numLetters = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> 6;
    case TUESDAY                -> 7;
    case THURSDAY, SATURDAY     -> 8;
    case WEDNESDAY              -> 9;
};

//somethimes we need multiple lines : don't forget to break the result ;)
int j = switch (day) {
    case MONDAY  -> 0;
    case TUESDAY -> 1;
    default      -> {
        int k = day.toString().length();
        int result = f(k);
        break result;
    }
};

Java 12

  • String::align et String::indent 
  • String::transform 
  • Files::isSameFile 
  • Collectors::teeing
  • java.text.CompactNumberFormat : formater des nombres dans une forme compacte (norme LDML)
    1000 -> 1K, 1000000 -> 1M, …

Java 12 : Shenandoah GC (JEP 189)

  • Développé par Redhat 
  • Concurrent à l’application
  • Promets des pauses minimale pour des heap de très grande taille

 

-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC.

Les fonctionnalités en cours de dev :

  • Raw String Literals (13 ?)
  • Pattern Matching
  • Datum
  • Continuation & Fibers
  • FFI
  • Value Types
  • ...

Migration

Migration

  1. Téléchargez un JDK 11 : https://jdk.java.net/11/ 
     
  2. Mettez à jour votre IDE
     
  3. Mettez à jour vos librairies
    1. Si nécessaire : ajoutez javax.xml.bind:jaxb-api:2.3.1
       
  4. Mettez à jour vos plugins Maven/Gradle
    1. Cobertura -> JaCoCO
       
  5. Mettez à jour vos Dockerfile :
    FROM openjdk/11
    FROM adoptopenjdk/openjdk11:x86_64-alpine-jdk-11.0.1.13-slim

     

Crédits / Biblio.

Merci !

&

Question ?

Loïc Mathieu -        @loicmathieu

Un petit dojo est dispo ici : https://github.com/loicmathieu/dojo-java-9-10-11

Java 9 to 11

By loicmathieu

Java 9 to 11

Les nouveautés de Java 9, 10 et 11 pour les dévelopeurs

  • 1,369