Publié par

Il y a 4 semaines -

Temps de lecture 7 minutes

De Java 8 à 11 : nouveautés et conseils pour migrer

Depuis qu’Oracle a annoncé vouloir accélérer le développement de Java, on a connu trois nouvelles versions du JDK en l’espace d’un an seulement (une nouvelle release tous les six mois).

Cette nouvelle cadence de release peut-être angoissante lorsqu’on est habitué à avoir une nouvelle version de Java tous les trois ans.

Cet article a pour objectif de lister les principales nouveautés entre les JDK 8 et 11, suivis de conseils pour migrer votre application à la dernière version de Java.

Support des différentes versions

Oracle assure sortir une version bénéficiant du LTS (long-term support) tous les trois ans, pour une durée de trois ans. Ainsi, le support commercial de Java 8 (qui date de mars 2014, déjà) va se terminer en janvier 2019 (décembre 2020 pour le non commercial et septembre 2022 pour OpenJDK).

Java 11 étant la nouvelle version LTS (jusqu’à septembre 2022 pour OpenJDK, Oracle n’ayant pas encore annoncé de date), il est donc temps de migrer !

Les nouveautés depuis Java 8

Java 9

Jigsaw, le système modulaire

Probablement la plus grosse nouveauté de Java 9, Jigsaw a pour objectif de rendre modulaire votre application.

Le but premier est de réduire la taille des applications en n’embarquant que les dépendances nécessaires, afin de déployer le logiciel sur des environnements où l’espace de stockage peut coûter cher, comme l’IoT ou le cloud.

Pour en profiter, il faut ajouter un fichier nommé module-info.java à la racine du JAR :

module fr.xebia.application.infra {
    requires fr.xebia.application.domain;
    exports fr.xebia.application.infra.api;
}

Méthodes privées dans les interfaces

Pour alléger les méthodes par défaut des interfaces depuis Java 8 et éviter la duplication de code, il désormais possible d’implémenter des méthodes private dans une interface :

public interface Logger {

	default void info(String s) {
		log(message, "INFO");
	}

	default void debug(String s) {
		log(message, "WARN");
	}

	private void log(String s, String visibility) {
		// Do something
	}
}

Try-with-resources

Arrivé avec le JDK 7, le try-with-resources permet d’instancier des objets implémentant java.lang.AutoCloseable (et par conséquent tous les objets qui implémentent java.io.Closeable) sans avoir à explicitement appeler la méthode close(). Grace à Java 9, on peut désormais utiliser des variables Closeable instanciées en dehors d’un bloc try-with-resources :

FileReader reader = new FileReader("/path/to/file.csv");
try (reader) {
  // Do something
}

Instanciation de collections immuables

Un peu de sucre syntaxique cette fois ci, avec l’instanciation de collections immuables grandement facilitée :

List<String> list = List.of("Xebia", "love", "Java");
Set<Integer> set = Set.of(1, 2, 3, 4);
Map<Integer, String> map = Map.of(1, "Xebia", 2, "love", 3, "Java"); // Peut prendre jusqu'à dix entrées

L’annotation @Deprecated enrichie

Deux nouveaux attributs pour cette annotation :

@Deprecated(since="4.2" forRemoval=true)

API Flow

Java 9 intègre l’API Flow, vous permettant d’implémenter vos propres flux réactifs :

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");  
  }  
}  

JShell

Il s’agit du read-eval-print loop (REPL) de Java. Il permet ainsi de tester des instructions Java sans avoir à démarrer tout un programme.

Améliorations de l’API Stream

Quatre nouvelles méthodes ont vu le jour dans l’API Stream :

Stream.of("a", "b", "c", "d").takeWhile(s -> !s.equals("c")).forEach(System.out::println); // Affiche "ab"

Stream.of("a", "b", "c", "d").dropWhile(s -> !s.equals("c")).forEach(System.out::println); // Affiche "cd"

Stream.ofNullable(null); // Retourne un Stream vide, sans NullPointerException évidemment

IntStream.iterate(1, i -> i < 10, i -> i + 1); // Équivalent à une boucle "for" de 0 à 9

Java 10

Inférence de type

À ne pas confondre avec des variables non typées, Java 10 intègre l’inférence de type afin de gagner en lisibilité et éviter ainsi la redondance :

List<String> list = List.of("Xebia", "love", "Java");
// Peut aussi s'écrire
var otherList = List.of("Xebia", "love", "Java");

Instanciation de collections immuables, encore…

Tout d’abord, petit point sur la copie de List avec Collections#unmodifiableList :

var original = new ArrayList<>(Lists.of("Xebia", "love", "Java"));
var unmodifiable = Collections.unmodifiableList(original);
original.set(2, "Scala");
System.out.println(unmodifiable.get(2)); // Affiche Scala, et non Java car Collections#unmodifiableList retourne une VUE non modifiable de la liste d'origine

Avec Java 10, pour copier une List sans craindre de modifier la copie quand la List d’origine est modifiée, la méthode copyOf est apparue :

var original = new ArrayList<>(Lists.of("Xebia", "love", "Java"));
var copy = List.copyOf(original);
original.set(2, "Scala");
System.out.println(copy.get(2)); // Affiche Java

De plus, de nouveaux Collectors ont été crée : toUnmodifiableList, toUnmodifiableSet, et toUnmodifiableMap.

Java 11

Inférence de type pour les paramètres de lambdas

Java 10 a apporté les var, mais on ne pouvait pas les utiliser dans les paramètres des expressions lambda. C’est maintenant corrigé avec Java 11 :

var original = Lists.of("Xebia", "love", "Java");
original.stream().filter((var s) -> s.contains("X")).forEach(System.out::println);

Certes, avec Java 8 il est inutile de spécifier le type. Mais celui-ci peut-être utile si l’on souhaite rajouter une annotation (@NonNull par exemple) sur le paramètre.

Nouveau client HTTP

Initialement prévu avec Java 9, ce nouveau client HTTP est finalement sorti de son incubateur avec Java 11 :

HttpRequest request = HttpRequest.newBuilder().uri(new URI("https://xebia.fr/endpoint")).GET().build();
 
HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandler.asString());

HttpClient est compatible avec la version 2 du protocole HTTP ainsi que les WebSocket.

De plus, il permet d’exécuter des requêtes de manière non bloquante (grâce à un BodyPublisher et un BodySubscriber, tous deux implémentant les interfaces de l’API Flow mise en place avec Java 9) :

httpClient.sendAsync(request, BodyHandlers.ofString()).thenAccept(response -> System.out.println(response.body()));

Conseils pour migrer

Mettez tout à jour

De votre IDE à vos dépendances externes, en passant par vos plugins de build et même Maven ou Gradle, mettez tout à jour pour vous assurer le minimum de problèmes lors du passage à Java 11. Même si, à moins d’utiliser des librairies qui modifient le bytecode ou font de la réflexion, dans la plupart des cas vous ne devriez pas rencontrer de difficultés, il est toujours préférable d’utiliser les librairies dans leur dernière version.

Rajoutez les dépendances manquantes

Si suite à la mise à jour d’une dépendance vous constatez des ClassNotFoundException, c’est qu’une classe n’est plus accessible suite à la modularisation d’une librairie tierce. Vous devez alors rajouter une nouvelle dépendance ou le module à votre projet.

Pas besoin de tout rendre modulaire

Java 9 et Jigsaw n’ont pas tué le Classpath de Java. Il n’est pas nécessaire de rendre modulaire votre application, à moins de développer une librairie pour laquelle vous devez assurer une compatibilité pour les versions 8 à 11 de Java. Cependant, le faire va réduire grandement le taille de votre livrable ainsi qu’augmenter la vitesse de démarrage de l’application.

var n’est pas (encore) un mot clef réservé

Si dans votre code, certaines variables s’appellent var, sachez que vous devriez leur trouver un meilleur nom ce n’est pas un mot réservé.

Conclusion

Théoriquement, le passage de Java 8 à 11 devrait être moins douloureux que le passage de Java 7 à 8. Si votre projet actuel sous Java 8 est correctement structuré et que vous maîtrisez vos dépendances, le passage vers Java 11 devrait se faire aisément.

Le seul point d’attention est la modularisation. Si vous souhaitez rendre modulaire votre application, je vous recommande fortement le tutoriel de Leonardo Zanivan où il fait évoluer le projet d’exemple Spring PetClinic du JDK 8 au JDK 11.

Publié par

Commentaire

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous recrutons

Être un Xebian, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.