JDK9, les nouveautés
Préalables pratiques
List, Set, Map of
Les modules, Java Platform Module System (JPMS) ou encore Jigsaw
Gestion des processus systèmes
Jshell
Nouvelles méthodes pour la classe Stream
Javadoc
_ underscore est un mot clé
La classe Optional se dote de nouvelles méthodes
Numérotation des versions
Algorithmes de hashages supplémentaires
Réduction de l'espace de stockage des chaines
Support d'unicode 8.0
http2
La classe InputStream se dote également de nouvelles méthodes
La classe Flow
, le producteur et le consommateur
Quelques points non détaillés
Alors que JDK 10 est sorti le 20 mars, je vous parle de JDK 9. Chaque chose en son temps…
Préalables pratiques
Pour tester JDK9, j'utilise Netbeans. Netbeans 8.1 et Netbeans 8.2 ne permettent pas d'utiliser JDK9. Il est nécessaire d'installer une version de développement comme indiqué dans le wiki et sur Stackoverflow pour celles et ceux qui préfèrent.
Il est nécessaire d'installer un JDK9. Sans blague. J'installe jdk-9.0.4.
Dans la suite, je supposerai que mon installation se trouve dans
/usr/lib/jvm/jdk9
. Je regarde l'arborescence — via la commande tree
— qui a l'allure suivante :
On constate d'emblée que l'organisation des fichiers a changé… ce qui est le corollaire immédiat de la notion de modules — voir ci-dessous — introduite dans le JDK.
Je repère aussi le fichier src.zip
que je décompresse aussitôt et que je
conserve au chaud pour plus tard. Son organisation est semblable
à l'organisation en module du répertoire jmods.
J'essaierai de citer les JEP, JDK Enhancement Proposals en rapport avec les différents points.
Dans certains exemples, je suppose l'existence d'une classe Video
représentant
une vidéo. Une vidéo a un auteur, un titre, un nombre de likes et un état
publiée ou pas. Je suppose également, l'existence d'une factory associées:
Videos
.
List, Set, Map of
Les interfaces List
, Set
et Map
reçoivent des méthodes of
(JEP269).
Ces méthodes retournent une List
, un Set
ou une Map
immuables
contenant les objets passés en paramètres. Elles sont bien sûr génériques
(generics). Je peux par exemple écrire pour un Set
:
Set s = Set.of("Vicky", "Jenny", "Karine");
alors que je devais écrire:
Set oldWay = new HashSet(
Arrays.asList("Vicky", "Jenny", "Karine"));
oldWay = Collections.unmodifiableSet(oldWay);
Pour une liste que je voudrais modifiable, je peux écrire;
List l = new ArrayList<>(
List.of(2,3,-5));
Je ne résiste pas à la tentation de vérifier1 que ces méthodes statiques ont
bien été ajoutées dans l'interface List
. Un find
plus tard, je trouve le
fichier source que je peux éditer.
$ find . -name List.java
./src/java.desktop/java/awt/List.java
./src/java.xml.bind/com/sun/xml/internal/bind/v2/schemagen/
xmlschema/List.java
./src/jdk.compiler/com/sun/tools/javac/util/List.java
./src/java.base/java/util/List.java
$ gvim src/java.base/java/util/List.java
Extraits:
public interface List extends Collection {
…
/**
* Returns an immutable list containing one element.
*
* See Immutable List Static Factory
* Methods for details.
*
* @param the {@code List}'s element type
* @param e1 the single element
* @return a {@code List} containing the specified element
* @throws NullPointerException if the element is {@code null}
*
* @since 9
*/
static List of(E e1) {
return new ImmutableCollections.List1<>(e1);
}
Les modules, Java Platform Module System (JPMS) ou encore Jigsaw
Comme on l'a vu, Java découpe son code en modules — Jigsaw de petit nom
— dépendants les uns des autres. Cette découpe en modules — outre qu'elle
réorganise l'arborescence des sources — permet de structurer le JDK d'une part
et d'autoriser uniquement le chargement des modules nécessaires d'autre part.
Cette restructuration du jdk et du jre est sensée offrir de meilleures
performances, plus de sécurité et une maintenance plus aisée.
Elle est décrite en partie dans JEP201, JEP261 et JEP200.
Les fichiers java — et particulièrement le bytecode — sont réorganisées.
Toutes les classes standards ne se trouvent plus dans rt.jar
et tools.jar
…
qui ont disparus JEP220. Les classes se trouvent dans le répertoire jmods
contenant des fichiers au format jmods.
Le code se trouvant dans les modules et dans les fichiers jar traditionnels basés sur le classpath peuvent coexister.
Cette notion de modules introduit:
une sorte d'édition des liens — optionnelle — entre la phase de compilation et celle d'exécution. Cette phase assemble les modules qui seront utilisés à l'exécution grâce à jlink.
la notion de fichier jar modulaire qui est un fichier jar contenant un fichier
module-info.class
à la racine. Ce fichier définit (voir plus bas) l'organisation du module.le format jmod, semblable au format jar, mais pouvant inclure du code natif et des fichiers de configuration. Voir jmod.
Pour définir un module, il est nécessaire d'ajouter un fichier
module-info.java
à la racine du projet contenant:
module org.example.my.module {
requires net.example.module.need;
export org.example.my.module.services;
}
module définit le module, requires précise quels sont les modules nécessaires et export présente publiquement les packages qui seront donc visibles.
Voir par exemple src/java.base/module-info.java
2.
À java
et javac
s'ajoutent deux commandes:
jlink
assemble des modules;jdeps
informe sur les dépendances.
Exemple, Hello, modular world
J'ai essayé d'utiliser Netbeans pour créer un module avec un new Java Modular Project, mais ça n'a pas été concluant.
Netbeans me crée bien un projet
auquel je peux ajouter un module. Il me crée alors un fichier module-info.java
que je peux compléter. Je crée un package, j'y place une classe et je clique
sur "Run project…". Netbeans me demande de choisir une classe principale et
tout roule… sauf si je veux lancer le projet en dehors de Netbeans. La commande
qu'il me propose — et d'autres variantes — ne fonctionne pas.
Reprenons depuis le début en créant un projet tout à fait standard cette fois.
- Création d'un projet
Ajout d'un package contenant une classe
Hello
et ce code:package be.example.hello; public class Hello { public static void main(String[] args) { System.out.println("Hello, modular world"); } }
Clic "Run" et ajout de la classe comme classe principale.
Ajout d'un fichier
module-info
via Netbeans. Il le crée en choisissant le nom du module en fonction du nom du projet:module Jdk9Modular { }
Ajout de la ligne
exports be.example.hello;
clean and build et Netbeans me propose une commande à exécuter. Tout roule…
java -p /elsewhere/jdk9-modular/dist/jdk9-modular.jar -m Jdk9Modular
Et si je voulais entrer les commandes « à la main » dans mon terminal. Je me base sur cette documentation que j'adapte à mon projet Netbeans et je suppose que je me trouve à la racine de mon projet.
javac -d build/classes
src/be.example.hello/classes/module-info.java
src/be.example.hello/classes/be/example/hello/Hello.java
jar --create
--file build/hello-modular-world.jar
--main-class be.example.hello.Hello
-C build/classes .
java
--module-path build/hello-modular-world.jar
--module be.example.hello
java
-p build/hello-modular-world.jar
-m be.example.hello
Les deux dernières commandes sont équivalentes. Notons qu'il n'est plus
nécessaire d'écrire un manifest, il suffit de renseigner la classe principale
avec --main-class
.
Gestion des processus systèmes
Une API dédiée à la gestion des processus et décrite dans JEP102. Avant JDK9, il était possible de lancer des processus mais pas de les contrôler ensuite.
Par exemple:
Runtime.getRuntime().exec("/usr/bin/xeyes");
ProcessBuilder pb = new ProcessBuilder("/usr/bin/xeyes");
pb.start();
La classe ProcessHandle
offre moult méthodes pour contrôler un processus.
Ces méthodes lancent des RuntimeException
ce qui est plus dans le mouvement
actuel par rapport aux exceptions contrôlées.
Je peux accéder au processus en cours mais également aux parents et aux enfants.
Si l'on compare à ProcessBuilder
c'est beaucoup plus complet.
Puisque l'on a accès à une méthode destroy
et à notre pid
, essayons de nous
suicider. Je sais, c'est triste… et Java nous en empêche. Tout va bien ;-)
ProcessHandle me = ProcessHandle.current();
System.out.printf("My process id: %d\n", me.pid());
System.out.printf("I'll try to kill myself (so sad)");
me.destroy();
Jshell
jShell est une boucle REPL (Read, Evaluate, Print, Loop) proposant une sorte
de shell en Java (un peu à l'instar de Python). jShell propose une série de
commandes et une autocomplétion avec la touche [TAB]. Par exemple: list
liste
les instructions entrées depuis le début de la session, vars
liste les
variables déclarées, history
pour l'historique, etc.
Extrait de l'aide:
jshell> /help
| Type a Java language expression, statement, or declaration.
| Or type one of the following commands:
| /list [<name or id>|-all|-start]
| list the source you have typed
| /edit <name or id>
| edit a source entry referenced by name or id
| … <cut>
Pour quitter, c'est /exit. Voir ci-dessous pour un exemple de « séance jShell ».
Pour voir le contenu d'une variable, inutile d'écrire System.out.print
, son
nom suffit:
jshell> double var = 3.14;
var ==> 3.14
jshell> System.out.print
print( printf( println(
jshell> System.out.println(var);
3.14
jshell> var
var ==> 3.14
Pour utiliser une classe d'un package spécifique, il y a 3 manières de faire — merci stacxoverflow:
- mettre à jour son CLASSPATH avant de lancer JShell;
utiliser l'option idoine de la commande jshell;
jshell --class-path beautifuljar.jar
utiliser la commande interne /env
/env -class-path beautifuljar.jar
Nouvelles méthodes pour la classe Stream
Quatre nouvelles méthodes pour Stream
: takeWhile
, dropWhile
,
ofNullable
et iterate
.
takeWhile — et c'est pareil pour dropWhile — est une méthode qui va prendre les éléments tant que la condition est respectée et c'est en ça qu'elle diffère de filter qui parcourt tout le flux.
Attention, si le flux n'est pas ordonné, take|dropWhile
retourne n'importe quel
sous-ensemble correspondant à la condition… même si l'on peut supposer que le
développeur s'arrêtera dès qu'il aura trouvé un faux et ne retournera pas un
autre sous-ensemble du flux.
If this stream is unordered, and some (but not all) elements of this stream match the given predicate, then the behavior of this operation is nondeterministic; it is free to take any subset of matching elements (which includes the empty set).
Extrait de la Javadoc
Par exemple:
System.out.print("\nFilter ");
Stream.of(1,2,3,1,2,3,1,2,3)
.filter(i -> i<3)
.forEach(i -> System.out.printf("%d ", i)); // Filter 1 2 1 2 1 2
System.out.print("\nTake while ");
Stream.of(1,2,3,1,2,3,1,2,3)
.takeWhile(i -> i<3)
.forEach(i -> System.out.printf("%d ", i)); // Take while 1 2
iterate voit apparaitre une nouvelle version avec un argument supplémentaire. Là ou l'on utilisait limit comme par exemple:
Stream.iterate(1, n -> n+1)
.limit(9)
.forEach(i -> System.out.printf("%d ", i));
on pourra directement mettre un predicate — bien plus général donc que limit — en argument. Un peu comme:
Stream.iterate(1, n -> n<10, n -> n+1)
.forEach(i -> System.out.printf("%d ", i));
ofNullable retourne un stream d'un élément ou un stream vide dans le cas d'un élément null. Ouais.
Javadoc
Remise en forme légère au niveau graphique de la javadoc, passage à HTML5, support des commentaires javadoc dans les déclarations de modules et ajout d'une zone de recherche. Et ça s'est bien.
_ underscore est un mot clé
Le caractère _
est devenu un keyword en java. Il ne peut plus être utilisé
comme un identifier.
La classe Optional se dote de nouvelles méthodes
Quatre nouvelles méthodes également pour la classe Optional
: ifPresent
,
ifPresentOrElse
, or
et stream
.
/*
* If factory fail to give a Video, create new video.
* Silly example.
*/
Optional
Numérotation des versions
La numérotation des versions est revue — on abandonne définitivement le 1.x — et suit le schéma suivant (JEP223):
$MAJOR.$MINOR.$SECURITY.$PATCH
Algorithmes de hashages supplémentaires
Ajout des algorithmes SHA3 (JEP283).
StringJoiner sj = new StringJoiner(" ", "Algorithms: ", "\n");
Security.getAlgorithms("MessageDigest")
.forEach(s -> sj.add(s));
System.out.print(sj);
Algorithms: SHA3-512 SHA-384 SHA SHA3-384 SHA-224 SHA-512/256
SHA-256 MD2 SHA-512/224 SHA3-256 SHA-512 MD5 SHA3-224
Pour calculer un hash, on peut écrire le code suivant et vérifier que le
hash est le même que celui fourni par echo -n "Beautifulmessage" | sha3sum
-a 512
:
String message = "Beautifulmessage";
MessageDigest md;
try {
md = MessageDigest.getInstance("SHA3-512");
md.update(message.getBytes());
byte[] bs = md.digest();
System.out.printf("Message: %s\n", message);
System.out.printf("Digest: ");
for (byte b : bs) {
System.out.printf("%02X", b);
}
System.out.println("");
} catch (NoSuchAlgorithmException ex) {
System.err.println("Algorithm error: " + ex.getMessage());
}
Réduction de l'espace de stockage des chaines
La représentation interne des chaines de caractères (strings) économise de l'espace. Là où un string était stocké dans un tableau de char (2 bytes par caractère), il l'est maintenant dans un tableau de byte et un attribut représentant l'encodage,encoding-flag field appelé coder (JEP254).
Les caractères constituants les chaines étaient codés en UTF-16, chaque caractère
occupant 1 char (parfois 2), soient 2 bytes (parfois 4). La plupart des
chaines de caractères ne contiennent que des caractères Latin-1 (ISO-8859-1). Un
caractère Latin-1 est codé sur 1 byte. La nouvelle classe String
stocke les
caractères soit en Latin-1 (ISO-8859-1) soit en UTF-16 en fonction du
contenu de la chaine.
Extraits de code source de la classe String sans les commentaires:
// JDK8
public final class String
implements java.io.Serializable, Comparable, CharSequence {
private final char value[];
private int hash; // Default to 0
private static final long serialVersionUID =
-6849794470754667710L;
// JDK9
public final class String
implements java.io.Serializable, Comparable, CharSequence {
private final byte[] value;
private final byte coder;
private int hash; // Default to 0
private static final long serialVersionUID =
-6849794470754667710L;
Java voit apparaitre deux nouvelles classes StringUTF16
et StringLatin1
et
la classe String
se meuble d'instructions de style:
if (isLatin1()) {
StringLatin1.foo();
} else {
StringUTF16.foo();
}
Support d'unicode 8.0
JDK8 supportait Unicode 6.2, JDK9 supporte Unicode 8.0 (JEP267). That's all.
http2
Java passe d'une implémentation de HTTP/1.1 à HTTP/2 (JEP110). HTTP/1.1 posait quelques problèmes pour un web du XXIe siècle
fins de lignes bloquants (head of lines blocking )
Les réponses du serveur sont reçues dans le même ordre qu'elles ont été envoyées. Avec HTTP/2, les réponses peuvent être multiplexées. S'il faut charger une grande page html contenant des images, il ne faudra plus attendre le chargement complet de la page avant le chargement des images.
nombre de connexions réduit;
A single-user client SHOULD NOT maintain more than 2 connections with any server or proxy. (RFC2616)
les headers sont toujours envoyés en texte. Avec HTTP/2, certains headers seront envoyés en binaire plutôt qu'en texte quand l'efficacité le demande;
HTTP/2 nous promet (voir cette note de blog (en)); la même API (same API), des requêtes moins couteuses (cheaper requests), un réseau plus convivial limitant les connexions (network and server friendliness), cache pushing (le serveur est capable de fournir des données sans requêtes du client), avec les connexions persistantes, le développeur pourra concevoir son application différemment (change your mind) et plus de chiffrement (more encryption).
L'API fournit trois classes; HTTPClient
, HTTPRequest
et HTTPResponse
. Ces
classes permettent d'instancier un client, de formuler une requête et d'attendre
une réponse. Facile. Faire une requête d'une page se fait avec un code
à l'allure suivante (source):
try {
HttpClient client = HttpClient
.newBuilder()
.version(HttpClient.Version.HTTP_2)
.build();
HttpRequest request = HttpRequest
.newBuilder(new URI("http://pit.namok.be"))
.GET()
.build();
HttpResponse response = client
.send(request, HttpResponse.BodyHandler.asString());
System.out.println(response.statusCode());
System.out.println(response.body());
} catch ( ex) {
ex.printStackTrace();
}
Les classes HttpClient
et HttpRequest
ont une méthode newBuilder
acceptant
toute une série de paramètres (version…) qu'il suffit de chainer avant d'appeler
la méthode build
qui construira l'objet.
Faire une requête https est aussi simple que l'ajout d'un s dans l'url.
Pour stocker le résultat de la requête dans un fichier il faudra le signaler via
le paramètre BodyHandler. BodyHandler.asFile(…)
. Par exemple:
try {
HttpClient client = HttpClient
.newBuilder()
.version(HttpClient.Version.HTTP_2)
.build();
HttpRequest request = HttpRequest
.newBuilder(new URI("https://pit.namok.be"))
.GET()
.build();
Path tempFile = Files.createTempFile("http2-test", ".html");
HttpResponse response = client
.send(request,
HttpResponse.BodyHandler.asFile(tempFile));
System.out.println(response.statusCode() + "\n"
+ response.body());
} catch ( ex) {
ex.printStackTrace();
}
Le dernier exemple montre comment faire une requête asynchrone cette fois. En utilisant les lambdas, c'est impressionnant comme c'est facile à écrire.
try {
HttpClient client = HttpClient
.newBuilder()
.version(HttpClient.Version.HTTP_2)
.build();
HttpRequest request = HttpRequest
.newBuilder(new URI("https://pit.namok.be"))
.GET()
.build();
Path tempFile = Files.createTempFile("http2-test", ".html");
CompletableFuture> futureResponse = client
.sendAsync(request,
HttpResponse.BodyHandler.asFile(tempFile))
.orTimeout(2000, TimeUnit.MILLISECONDS)
.whenComplete((r, e)
-> System.out.printf("Callback status %d %s\n",
r.statusCode(),
r.body()))
.exceptionally((e) -> {
System.out.printf("Exceptionally %s\n",
e.getClass());
return null;
});
futureResponse.join();
} catch (IOException
| URISyntaxException ex) {
ex.printStackTrace();
}
Notez que le client HTTP est un « module incubateur » (incubator module). Ce qui signifie que:
le module est appelé
jdk.incubator.httpclient
et les classesjdk.incubator
.http.Http*
. Ce module doit être ajouté par le biais d'un fichiermodule-info
qui aura la forme suivante:module pbt.trys { requires jdk.incubator.httpclient; }
les classes qui incubent ne se trouveront pas dans Java 10 et peuvent être modifiées. Dans ce cas précis, le module s'appellera
java.httpclient
et les classes (probablement)java.http.Http*
.
La classe InputStream se dote également de nouvelles méthodes
Trois nouvelles méthodes utilitaires pour la classe InputStream
:
readAllBytes
, readNBytes
et transferTo
.
Ces méthodes appellent peu de commentaires. Les deux premières permettent la lecture d'un ficher dans un tableau de bytes et sont plutôt destinées aux petits fichiers et la dernière sert à envoyer directement le flux d'entrée vers un flux de sortie.
La classe Flow
, le producteur et le consommateur
Ce design pattern est très simple; un producteur qui produit des tâches, un consommateur qui exécute les tâches une par une et une file d'attente dans laquelle le producteur ajoute ses tâches. Le consommateur les retire une par une. Il peut bien sûr y avoir plusieurs producteurs et plusieurs consommateurs.
C'est la gestion de la file d'attente qui doit être soignée. Elle
est en forte concurrence d'accès. Actuellement, si je devais coder ce design,
je mettrais l'accès à la file d'attente en section critique (via un
synchronized, ou en utilisant une BlockingQueue
).
Java 9 introduit une nouvelle classe Flow
dans le package
java.util.concurrent
pour une mise en œuvre de ce pattern producteur
/ consommateur. Cette classe Flow
propose trois interfaces;
Flow.Publisher<T>
, Flow.Subscriber<T>
et Flow.Subscription
J'écris une classe implémentant Subscriber<T>
pour le consommateur. Cette
interface contient quatre méthodes assez naturelles; une pour s'enregistrer, une
pour faire le boulot, une pour gérer les erreurs et la dernière pour clôturer.
Je peux écrire un code à l'allure suivante:
public class VideoSubscriber implements Subscriber
Le producteur doit implémenter la classe Publisher<T>
. Il existe déjà une
classe l'implémentant. Il s'agit de SubmissionPublisher<T>
. Cette classe
propose — entre autres — une méthode submit
qui soumet une tâche. Je peux donc
directement écrire un main à l'allure suivante:
SubmissionPublisher
Teaser Java 10. Je pourrai bientôt écrire:
var publisher = new SubmissionPublisher
… mais chut :-)
En attendant, essayez d'ajouter des consommateurs et des producteurs. Toute la gestion est faite par Java.
Quelques points non détaillés
Multi-release jar files (JEP238);
Complète la structure des fichiers jar et autorise plusieurs releases différentes pour une classe. Par exemple JDK9 et <JDK9.
annotation
@Deprecated
;L'annotation se complète de paramètres. Par exemple la classe
java.lang.Compiler
package java.lang; /** * The {@code Compiler} class is provided to support * Java-to-native-code compilers and related services. * By design, the {@code Compiler} class does * … */ @Deprecated(since="9", forRemoval=true) public final class Compiler { … }
le moteur de rendu JavaFX et Java 2D est Marlin et plus Pisces ni Ductus (JEP265);
le garbage collector (ramasse-miettes) sera G1 et plus Parallel GC (JEP248);
à noter que l'on peut écrire des méthodes privées dans les interfaces… utiles aux default methods (JEP213);
l'API
Applet
est dépréciée @Deprecated(since = "9") (JEP289);suppression de certains outils obsolètes ou ajoutés à l'époque comme simples outils de démonstration; hprof, jhat (JEP240, JEP241);
Pour aller plus loin, vous pouvez consulter les changements complets et classés chez Oracle. Et reprendre ensuite une activité normale.
Crédit photo chez DeviantArt par Pixel duster. Un « neuf » pour Java 9. Bon d'accord.