Développons en Java v 2.40 Copyright (C) 1999-2023 Jean-Michel DOUDOUX. |
|||||||
Niveau : | Fondamental |
Ce chapitre présente quelques concepts de base utilisés en Java relatifs à la compilation et l'exécution d'applications, notamment, les notions de classpath, de packages et d'archives de déploiement jar.
Ce chapitre contient plusieurs sections :
La plate-forme Java utilise quelques notions de base lors de sa mise en oeuvre, notamment :
Un programme Java est composé d'un ou plus généralement plusieurs fichiers source. N'importe quel éditeur de texte peut être utilisé pour éditer un fichier source Java.
Ces fichiers source possèdent l'extension .java. Ils peuvent contenir une ou plusieurs classes ou interfaces mais il ne peut y avoir qu'une seule classe ou interface déclarée publique par fichier. Le nom de ce fichier source doit obligatoirement correspondre à la casse près au nom de cette entité publique suivi de l'extension .java
Il est nécessaire de compiler le source pour le transformer en J-code ou bytecode Java qui sera lui exécuté par la machine virtuelle. Pour être compilé, le programme doit être enregistré au format de caractères Unicode : une conversion automatique est faite par le JDK si nécessaire.
Un compilateur Java, par exemple l'outil javac fourni avec le JDK est utilisé pour compiler chaque fichier source en fichier de classe possédant l'extension .class. Cette compilation génère pour chaque fichier source un ou plusieurs fichiers .class qui contiennent du bytecode.
Exemple : |
public class MaClasse {
public static void main(String[] args) {
System.out.println("Bonjour");
}
}
Résultat : |
C:\TEMP>javac MaClasse.java
C:\TEMP>dir MaClas*
Volume in drive C has no label.
Volume Serial Number is 1E06-2R43
Directory of C:\TEMP
31/07/2007 13:34 417 MaClasse.class
31/07/2007 13:34 117 MaClasse.java
Le compilateur génère autant de fichiers .class que de classes et interfaces définies dans chaque fichier source.
Exemple : |
public class MaClasse {
public static void main(String[] args) {
System.out.println("Bonjour");
}
}
class MonAutreClasse {
public static void afficher(String message) {
System.out.println(message);
}
}
Résultat : |
C:\TEMP>dir *.class
Volume in drive C has no label.
Volume Serial Number is 1E06-2R43
Directory of C:\TEMP
31/07/2007 13:40 417 MaClasse.class
31/07/2007 13:40 388 MonAutreClasse.class
Pour exécuter une application, la classe servant de point d'entrée doit obligatoirement contenir une méthode ayant la signature public static void main(String[] args). Il est alors possible de fournir cette classe à la JVM qui va charger le ou les fichiers .class utiles à l'application et exécuter le code.
Exemple : |
C:\TEMP>java MaClasse
Bonjour
Pour les classes anonymes, le compilateur génère un nom de fichier constitué du nom de la classe englobante suffixé par $ et un numéro séquentiel.
Exemple : |
import javax.swing.JFrame;
import java.awt.event.*;
public class MonApplication {
public static void main(String[] args) {
MaFenetre f = new MaFenetre();
f.afficher();
}
}
class MaFenetre {
JFrame mainFrame = null;
public MaFenetre() {
mainFrame = new JFrame();
mainFrame.setTitle("Mon application");
mainFrame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent ev) {
System.exit(0);
}
});
mainFrame.setSize(320, 240);
}
public void afficher() {
mainFrame.setVisible(true);
}
}
Résultat : |
C:\TEMP>javac MonApplication.java
C:\TEMP>dir *.class
Volume in drive C has no label.
Volume Serial Number is 1E06-2R43
Directory of C:\TEMP
31/07/2007 13:50 494 MaFenetre$1.class
31/07/2007 13:50 687 MaFenetre.class
31/07/2007 13:50 334 MonApplication.class
Une classe anonyme peut elle-même définir une classe : dans ce cas le nom du fichier de classe sera celui de la classe anonyme suffixé par le caractère $ et le nom de la classe
Exemple : |
import javax.swing.JFrame;
import java.awt.event.*;
public class MonApplication {
public static void main(String[] args) {
MaFenetre f = new MaFenetre();
f.afficher();
}
}
class MaFenetre {
JFrame mainFrame = null;
public MaFenetre() {
mainFrame = new JFrame();
mainFrame.setTitle("Mon application");
mainFrame.addWindowListener(new WindowAdapter() {
class MonAutreClasse {
public void afficher(String message) {
System.out.println(message);
}
}
public void windowClosing(WindowEvent ev) {
System.exit(0);
}
});
mainFrame.setSize(320, 240);
}
public void afficher() {
mainFrame.setVisible(true);
}
}
Résultat : |
C:\TEMP>javac MonApplication.java
C:\TEMP>dir *.class
Volume in drive C has no label.
Volume Serial Number is 1E06-2R43
Directory of C:\TEMP
31/07/2007 13:53 549 MaFenetre$1$MonAutreClasse.class
31/07/2007 13:53 555 MaFenetre$1.class
31/07/2007 13:53 687 MaFenetre.class
31/07/2007 13:53 334 MonApplication.class
Les fichiers sources peuvent être organisés en packages. Les packages définissent une hiérarchie de noms, chaque nom étant séparé par le caractère point. Le nom d'un package est lié à une arborescence de sous-répertoires correspondant à ce nom.
Ceci permet de structurer les sources d'une application car une application peut rapidement contenir plusieurs centaines voire milliers de fichiers source. Les packages permettent aussi d'assurer l'unicité d'une classe grâce à son nom pleinement qualifié (nom du package suivi du caractère «.» suivi du nom de la classe).
L'API Java est organisée en packages répartis en trois grands ensembles :
Les principaux packages standards de Java 6 sont :
java.applet |
Création d'applets |
java.awt |
Création d'interfaces graphiques avec AWT |
java.io |
Accès aux flux entrants et sortants |
java.lang |
Classes et interfaces fondamentales |
java.math |
Opérations mathématiques |
java.net |
Accès aux réseaux |
java.nio |
API NIO |
java.rmi |
API RMI (invocation de méthodes distantes) |
java.security |
Mise en oeuvre de fonctionnalités concernant la sécurité |
java.sql |
API JDBC (accès aux bases de données) |
java.util |
Utilitaires (collections, internationalisation, logging, expressions régulières,...). |
Les principaux packages d'extensions de Java 6 sont :
javax.crypto |
Cryptographie |
javax.jws |
Services web |
javax.management |
API JMX |
javax.naming |
API JNDI (Accès aux annuaires) |
javax.rmi |
RMI-IIOP |
javax.script |
API Scripting |
javax.security |
Authentification et habilitations |
javax.swing |
API Swing pour le développement d'interfaces graphiques |
javax.tools |
API pour l'accès à certains outils comme le compilateur par exemple |
javax.xml.bind |
API JAXB pour la mapping objet/XML |
javax.xml.soap |
Création de messages SOAP |
javax.xml.stream |
API StAX (traitement de documents XML) |
javax.xml.transform |
Transformation de documents XML |
javax.xml.validation |
Validation de documents XML |
javax.xml.ws |
API JAX-WS (service web) |
Les principaux packages tiers de Java 6 sont :
org.omg.CORBA |
Mise en oeuvre de CORBA |
org.w3c.dom |
Traitement de documents XML avec DOM |
org.xml.sax |
Traitement de documents XML avec SAX |
Le package est précisé dans le fichier source grâce à l'instruction package. Le fichier doit donc, dans ce cas, être stocké dans une arborescence de répertoires qui correspond au nom du package.
Exemple : |
package fr.jmdoudoux.dej;
public class MaCLasseTest {
public static void main() {
System.out.println("Bonjour");
}
}
Si les sources de l'application sont dans le répertoire C:\Documents and Settings\jm\workspace\Tests, alors le fichier MaCLasseTest.java doit être dans le répertoire C:\Documents and Settings\jm\workspace\Tests\com\jmdoudoux\test.
Si aucun package n'est précisé, alors c'est le package par défaut (correspondant au répertoire courant) qui est utilisé. Ce n'est pas une bonne pratique d'utiliser le package par défaut sauf pour des tests.
Dans le code source, pour éviter d'avoir à utiliser les noms pleinement qualifiés des classes, il est possible d'utiliser l'instruction import suivi d'un nom de package suivi d'un caractère «.» et du nom d'une classe ou du caractère «*»
Exemple : |
import javax.swing.JFrame;
import java.awt.event.*;
Remarque : par défaut le package java.lang est toujours importé par le compilateur.
Il est possible de créer une enveloppe qui va contenir tous les fichiers d'une application Java ou une portion de cette application dans un fichier .jar (Java archive). Ceci inclus : l'arborescence des packages, les fichiers .class, les fichiers de ressources (images, configuration, ...), ... Un fichier .jar est physiquement une archive de type Zip qui contient tous ces éléments.
L'outil jar fourni avec le jdk permet de manipuler les fichiers jar.
Exemple : |
C:\TEMP>jar cvf MonApplication.jar *.class
manifest ajoutÚ
ajout : MaFenetre$1$MonAutreClasse.class (entrÚe = 549) (sortie = 361) (34% comp
ressÚs)
ajout : MaFenetre$1.class (entrÚe = 555) (sortie = 368) (33% compressÚs)
ajout : MaFenetre.class (entrÚe = 687) (sortie = 467) (32% compressÚs)
ajout : MonApplication.class (entrÚe = 334) (sortie = 251) (24% compressÚs)
Le fichier .jar peut alors être diffusé et exécuté s'il contient au moins une classe avec une méthode main().
Exemple : déplacement du jar pour être sûre qu'il n'utilise pas de classe du répertoire et exécution |
C:\TEMP>copy MonApplication.jar ..
1 file(s) copied.
C:\TEMP>cd ..
C:\>java -cp MonApplication.jar MonApplication
Remarque : un fichier .jar peut contenir plusieurs packages.
Le fichier jar peut inclure un fichier manifest qui permet de préciser des informations d'exécution sur le fichier jar (classe principale à exécuter, classpath, ...) : ceci permet d'exécuter directement l'application en double-cliquant sur le fichier .jar.
A l'exécution, la JVM et les outils du JDK recherchent les classes requises dans :
Important : il n'est pas recommandé d'ajouter des classes ou des bibliothèques dans les sous-répertoires du JDK.
La notion de classpath est importante car elle est toujours utilisée quel que soit l'emploi qui est fait de Java (ligne de commandes, IDE, script Ant, ...). Le classpath est sûrement la notion de base qui pose le plus de problèmes aux développeurs inexpérimentés en Java mais sa compréhension est absolument nécessaire.
Le classpath permet de préciser au compilateur et à la JVM où ils peuvent trouver les classes dont ils ont besoin pour la compilation et l'exécution d'une application. C'est un ensemble de chemins vers des répertoires ou des fichiers .jar dans lequel l'environnement d'exécution Java recherche les classes (celles de l'application mais aussi celles de tiers) et éventuellement des fichiers de ressources utiles à l'exécution de l'application. Ces classes ne concernent pas celles fournies par l'environnement d'exécution incluses dans le fichier rt.jar qui est implicitement utilisé par l'environnement.
Le classpath est constitué de chemins vers des répertoires et/ou des archives sous la forme de fichiers .jar ou .zip. Chaque élément du classpath peut donc être :
Les éléments du classpath qui ne sont pas des répertoires ou des fichiers .jar ou .zip sont ignorés.
Ces chemins peuvent être absolus ou relatifs. Chaque chemin est séparé par un caractère spécifique au système d'exploitation utilisé : point-virgule sous Windows et deux-points sous Unix par exemple.
Exemple sous Windows : |
.;C:\java\tests\bin;C:\java\lib\log4j-1.2.11.jar;"C:\Program Files\tests\tests.jar"
Dans cet exemple, le classpath est composé de quatre entités :
Remarque : sous Windows, il est possible d'utiliser le caractère / ou \ comme séparateur d'arborescence de répertoires.
Par défaut, si aucun classpath n'est défini, le classpath est composé uniquement du répertoire courant. Une redéfinition du classpath (avec l'option -classpath ou -cp ou la variable d'environnement système CLASSPATH) inhibe cette valeur par défaut.
La recherche d'une classe se fait dans l'ordre des différents chemins du classpath : cet ordre est donc important surtout si une bibliothèque est précisée dans deux chemins. Dans ce cas, c'est le premier trouvé dans l'ordre précisé qui sera utilisé, ce qui peut être à l'origine de problèmes.
Le classpath peut être défini à plusieurs niveaux :
Exemple sous Windows
Il faut utiliser la commande set pour définir la variable d'environnement CLASSPATH. Le séparateur entre chaque élément du classpath est le caractère point-virgule. Il ne faut pas mettre d'espace de part et d'autre du signe égal.
Exemple : |
set CLASSPATH=C:\java\classes;C:\java\lib;C:\java\lib\mysql.jar;.
Sous Windows 9x : il est possible d'ajouter une ligne définissant la variable d'environnement dans le fichier autoexec.bat :
Exemple : |
set CLASSPATH=.;c:\java\lib\mysql.jar;%CLASSPATH%
Sous Windows NT/2000/XP : il faut lancer l'application démarrer/paramètre/panneau de configuration/système, ouvrir l'onglet "avancé" et cliquer sur le bouton "Variables d'environnement". Il faut ajouter ou modifier la variable CLASSPATH avec comme valeur les différents éléments du classpath séparés chacun par un caractère point virgule.
Exemple sous Unix (interpréteur bash) :
Exemple : |
CLASSPATH=.:./lib/log4j-1.2.11.jar
export CLASSPATH;
L'utilisation de la variable système CLASSPATH est pratique car elle évite d'avoir à définir le classpath pour compiler ou exécuter mais c'est une mauvaise pratique car cela peut engendrer des problèmes :
Si la JVM ou le compilateur n'arrive pas à trouver une classe dans le classpath, une exception de type java.lang.ClassNotFoundException à la compilation ou java.lang.NoClassDefFoundError à l'exécution est levée.
Exemple : |
package fr.jmdoudoux.dej;
public class MaCLasseTest {
public static void main() {
System.out.println("Bonjour");
}
}
Le fichier MaCLassTest.class issu de la compilation est stocké dans le répertoire C:\Documents and Settings\jm\workspace\Tests\com\jmdoudoux\test
En débutant en Java, il est fréquent de se placer dans le répertoire qui contient le fichier .class et de lancer la JVM.
Exemple : |
C:\Documents and Settings\jmd\workspace\Tests\com\jmdoudoux\test>java MaCLasseTe
st
Exception in thread "main" java.lang.NoClassDefFoundError: MaCLasseTest (wrong n
ame: com/jmdoudoux/test/MaCLasseTest)
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(Unknown Source)
at java.security.SecureClassLoader.defineClass(Unknown Source)
at java.net.URLClassLoader.defineClass(Unknown Source)
at java.net.URLClassLoader.access$000(Unknown Source)
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClassInternal(Unknown Source)
Cela ne fonctionne pas car la JVM cherche à partir du répertoire courant (défini dans le classpath par défaut) une classe qui soit définie dans le package par défaut (aucun nom de package précisé). Hors dans l'exemple, la classe est définie dans le package fr.jmdoudoux.dej.
Une autre erreur assez fréquente est de se déplacer dans le répertoire qui contient le premier répertoire du package
Exemple : |
C:\Documents and Settings\jm\workspace\Tests\fr\jmdoudoux\test>cd ../../..
C:\Documents and Settings\jm\workspace\Tests>java MaCLasseTest
Exception in thread "main" java.lang.NoClassDefFoundError: MaCLasseTest
Dans ce cas, cela ne fonctionne pas car le nom de la classe n'est pas pleinement qualifié
Exemple : |
C:\Documents and Settings\jmd\workspace\Tests>java fr.jmdoudoux.dej.MaCL
asseTest
Bonjour
En précisant le nom pleinement qualifié de la classe, l'application est exécutée.
Si le classpath est redéfini, il ne faut pas oublier d'ajouter le répertoire courant au besoin en utilisant le caractère point. Cette pratique n'est cependant pas recommandée.
Exemple : |
C:\Documents and Settings\jmd\workspace\Tests>java -cp test.jar fr.jmdoudouxde
j.MaCLasseTest
Exception in thread "main" java.lang.NoClassDefFoundError: com/jmdoudoux/test/Ma
CLasseTest
C:\Documents and Settings\jmd\workspace\Tests>java -cp test.jar;. fr.jmdoudoux
test.MaCLasseTest
Bonjour
Les IDE fournissent tous des facilités pour gérer le classpath. Cependant en débutant, il est préférable d'utiliser les outils en ligne de commande pour bien comprendre le fonctionnement du classpath.
Dans cette section, une application est contenue dans le répertoire c:\java\tests. Elle est composée de la classe fr.jmdoudoux.dej.MaClasse.java.
Exemple : |
package fr.jmdoudoux.dej;
public class MaClasse {
public static void main(
String[] args) {
System.out.println("Bonjour");
}
}
La structure des répertoires et fichiers de l'application est la suivante :
Pour compiler la classe MaClasse, il faut utiliser la commande :
Exemple : |
C:\java\tests>javac com/jmdoudoux/test/MaClasse.java
Le fichier MaClasse.class est créé
Pour exécuter la classe, il faut utiliser la commande
Exemple : |
C:\java\tests>java fr.jmdoudoux.dej.MaClasse
Bonjour
Remarque : il est inutile de spécifier le classpath puisque celui-ci n'est composé que du répertoire courant qui correspond au classpath par défaut.
Il est cependant possible de le préciser explicitement
Exemple : |
C:\java\tests>java -cp . fr.jmdoudoux.dej.MaClasse
Bonjour
C:\java\tests>java -cp c:/java/tests fr.jmdoudoux.dej.MaClasse
Bonjour
Il est possible de définir le classpath en utilisant la variable d'environnement système CLASSPATH.
Exemple : le fichier run.bat |
@echo off
set CLASSPATH=c:/java/tests
javac com/jmdoudoux/test/MaClasse.java
java fr.jmdoudoux.dej.MaClasse
Ce script redéfinit la variable CLASSPATH, exécute le compilateur javac et l'interpréteur java pour exécuter la classe. Ces deux commandes utilisent la variable CLASSPATH.
Exemple : |
C:\java\tests>run.bat
Bonjour
L'exemple de cette section va utiliser la bibliothèque log4j.
Exemple : |
package fr.jmdoudoux.dej;
import org.apache.log4j.*;
public class MaClasse {
static Logger logger = Logger.getLogger(MaClasse.class);
public static void main(
String[] args) {
PropertyConfigurator.configure("log4j.properties");
logger.info("Bonjour");
}
}
Le fichier jar de log4j est stocké dans le sous-répertoire lib. Le fichier de configuration log4.properties est dans le répertoire principal de l'application puisqu'il est inclus dans le classpath
Il est nécessaire de préciser dans le classpath le répertoire tests et le fichier jar de log4j.
Exemple : |
C:\java\tests>javac -cp c:/java/tests;c:/java/tests/lib/log4j-1.2.11.jar fr/jmd
oudoux/dej/MaClasse.java
C:\java\tests>java -cp c:/java/tests;c:/java/tests/lib/log4j-1.2.11.jar fr.jmdo
udoux.dej.MaClasse
[main] INFO fr.jmdoudoux.dej.MaClasse - Bonjour
Il est aussi possible d'utiliser la variable d'environnement système classpath.
Il est possible de préciser les bibliothèques requises dans le fichier manifest du fichier jar.
La propriété JAR-class-path va étendre le classpath mais uniquement pour les classes chargées à partir du jar. Les classes incluses dans le JAR-class-path sont chargées comme si elles étaient incluses dans le jar.
Exemple : le fichier manifest.mf |
Main-Class: fr.jmdoudoux.dej.MaClasse
Class-Path: lib/log4j-1.2.11.jar
La clé Class-Path permet de définir le classpath utilisé lors de l'exécution.
Remarques importantes : Il faut obligatoirement que le fichier manifest se termine par une ligne vide. Pour préciser plusieurs entités dans le classpath, il faut les séparer par un caractère espace.
La structure des répertoires et des fichiers est la suivante :
Pour créer l'archive jar, il faut utiliser l'outil jar en précisant les options de création, le nom du fichier .jar, le fichier manifest et les entités à inclure dans le fichier jar.
Exemple : |
C:\java\tests>jar cfm tests.jar manifest.mf com log4j.properties
Le fichier jar est créé
L'archive jar ne contient pas le sous-répertoire lib, donc il n'inclut pas la bibliothèque requise.
Pour exécuter l'application, il suffit d'utiliser l'interpréteur java avec l'option -jar
Exemple : |
C:\java\tests>java -jar tests.jar
[main] INFO fr.jmdoudoux.dej.MaClasse - Bonjour
Attention : les entités précisées dans le classpath du fichier manifest doivent exister pour permettre l'exécution de l'application.
Exemple : |
C:\java\tests>rename lib libx
C:\java\tests>java -jar tests.jar
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/log4j/Logg
er
at fr.jmdoudoux.dej.MaClasse.<clinit>(MaClasse.java:6)
Caused by: java.lang.ClassNotFoundException: org.apache.log4j.Logger
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClassInternal(Unknown Source)
... 1 more
Il suffit de créer une page HTML pouvant être très simple :
Exemple : |
<HTML>
<TITLE> test applet Java </TITLE>
<BODY>
<APPLET code="NomFichier.class" width="270" height="200">
</APPLET>
</BODY>
</HTML>
Il faut ensuite visualiser la page créée dans l'appletviewer ou dans un navigateur 32 bits compatible avec la version de Java dans laquelle l'applet est écrite.
Après Java 9, une nouvelle version majeure de Java est publiée tous les six mois. Ce nouveau rythme a un impact sur l'ensemble de l'écosystème et en particulier le délai pour livrer de nouvelles fonctionnalités dans le langage et dans les API. Avec une durée de six mois entre deux versions majeures du JDK, il est possible de proposer des fonctionnalités pour évaluation, de recueillir des commentaires et des retours suite à leurs utilisations, de les améliorer selon ceux qui sont fournis et finalement les rendre standard.
Ainsi certaines fonctionnalités du JDK sont proposées à la communauté pour permettre de fournir du feedback sur leurs mises en oeuvre.
Les nouvelles fonctionnalités non finales peuvent être livrées dans trois catégories :
Exemple de fonctionnalité en preview : les switch expressions
Exemple de fonctionnalité expérimentale : le ramasse-miettes ZGC
Des mesures de protection sont mise en oeuvre pour empêcher l'utiliser accidentellement de fonctionnalités non finales. C'est nécessaire car une fonctionnalité non définitive peut être différente lorsqu'elle devient définitive et permanente dans une version ultérieure de Java. D'autant que seules les fonctionnalités définitives et permanentes sont soumises aux règles strictes de rétrocompatibilité de Java.
Pour éviter toute utilisation involontaire, ces fonctionnalités sont désactivées par défaut :
Une fonctionnalité en preview est une nouvelle fonctionnalité du langage Java, de la machine virtuelle Java ou de l'API Java SE dont la conception, la spécification et l'implémentation sont complètes, mais qui n'est pas encore finale et permanente, ce qui signifie que la fonctionnalité peut évoluer avant d'être incluse dans la plate-forme Java de manière définitive et permanente ou même être retirée dans une version future du JDK. Les fonctionnalités en preview ne sont pas des versions bêta : elles sont publiées dans un état stable pour évaluation.
Elle est mise à disposition dans une version du JDK pour permettre aux développeurs de la tester et de l'évaluer en situation réelle afin de fournir des retours et des commentaires. Ceux-ci pourront éventuellement être pris en compte pour améliorer la fonctionnalité dans une future version du JDK. Le but d'une fonctionnalité en preview est donc de permettre aux développeurs de l'essayer et de fournir des retours et commentaires sur sa mise en oeuvre.
Ces retours d'informations et les commentaires recueillis sont évalués afin de pouvoir être éventuellement pris en compte pour apporter d'éventuels ajustements dans la release suivante en preview ou avant de devenir permanente et standard. En conséquence, la fonctionnalité peut se voir accorder le statut final et permanent (avec ou sans améliorations), ou être proposée pour une nouvelle période en preview (avec ou sans améliorations), ou même être supprimée.
Ces fonctionnalités sont publiées pour expérimentation, mais protégées contre une utilisation accidentelle avec des erreurs à la compilation et à l'exécution sans activation explicite des fonctionnalités en preview.
A partir de Java 12, chaque version de Java propose une ou plusieurs fonctionnalités en preview. La première fonctionnalité introduite en preview concerne les switch expressions livrées en preview en Java 12 (JEP 325) et Java 13 (JEP 354) avant de devenir standard en Java 14 (JEP 361).
Plusieurs projets livrent des fonctionnalités en preview notamment le projet Amber qui concerne des évolutions dans la syntaxe du langage Java. Il livre ses fonctionnalités au fur et à mesure, la plupart en preview avec plusieurs releases en preview, généralement au moins 2 (switch expressions, blocs de texte, records, pattern matching pour l'instruction instanceof, les classes scellées, ...).
Il est important de se rappeler qu'une fonctionnalité en preview sera dans la version suivante de Java :
Il n'est pas obligatoire ni nécessaire que toutes les nouvelles fonctionnalités du langage, de la JVM et des API soient disponibles initialement en tant que fonctionnalités en preview.
Le rôle et le cycle de vie des fonctionnalités en preview sont définis dans la JEP 12.
La JEP 12 a été introduite dans le JDK 12 pour proposer initialement deux types de fonctionnalités en preview dans la plateforme Java SE :
Dans le JDK 13, la JEP 12 a été améliorée pour reconnaître que les fonctionnalités du langage en preview sont parfois co-développées avec de nouvelles API. Cela a conduit à une taxonomie des API co-développées : "essential", "reflective" et "convenient".
Dans le JDK 15, la JEP 12 a été améliorée pour permettre un troisième type de fonctionnalité en preview dans la plate-forme Java SE : les API en preview. Les API en preview englobent l'idée d'API co-développées avec les fonctionnalités du langage en preview et permettent en outre la définition d'API Java SE non liées à d'autres fonctionnalités en preview. Cela a conduit à une taxonomie d'API en preview "essential", "reflective", "convenient" et "standalone".
Une fonctionnalité en preview peut donc être :
La conception, la spécification et l'implémentation sont achevées lors de leur diffusion. Elles permettent de proposer une période d'exposition et d'évaluation à grande échelle avant d'obtenir un statut définitif et permanent dans la plate-forme Java SE ou d'être affinées selon les retours obtenus ou même supprimées.
Les fonctionnalités en preview permettent de bénéficier d'une période d'exposition après que leurs spécifications et leurs implémentations soient stables. Elles permettent aux développeurs de les expérimenter et de fournir des retours et commentaires avant qu'elles n'atteignent un statut final et permanent dans la plate-forme Java SE.
Au final, la fonctionnalité se verra accorder un statut final et permanent (avec ou sans améliorations) ou sera supprimée.
Une fonctionnalité en preview doit respecter plusieurs critères :
Chaque fonctionnalité en preview est décrite dans une JDK Enhancement Proposal (JEP) qui définit sa portée et fournit sa description. Généralement, les JEP qui concernent une fonctionnalité en preview contiennent dans leur titre le nom de la fonctionnalité et « (Preview) / (Second Preview) / (Third Preview) / ...».
Toutes les fonctionnalités en preview doivent être désactivées par défaut dans une implémentation de Java SE et une implémentation doit offrir un mécanisme pour activer toutes les fonctionnalités en preview. Une implémentation ne doit pas permettre d'activer individuellement les fonctionnalités en preview, puisque toutes les fonctionnalités en preview ont le même statut dans la plate-forme Java SE.
Il n'y a pas de contraintes sur la forme d'une fonctionnalité en preview :
En Java 12 et 13, les API en preview sont marquées avec le mécanisme de dépréciation standard. Par conséquent, les API en preview ont été dépréciées à la naissance en utilisant l'annotation @Deprecated(forRemoval=true, since=...) lors de leur introduction. Cependant, cette approche basée sur la dépréciation a finalement été abandonnée car il était déroutant de voir un élément d'API introduit dans la même version de Java SE qu'il a été déprécié. Par exemple voir @since 13 et @Deprecated(forRemoval=true, since="13") sur le même élément d'une API en preview.
A partir de Java 14, les fonctionnalités en preview dans le JDK sont annotées avec @jdk.internal.javac.PreviewFeature. Elle indique que l'élément annoté est associé à une fonctionnalité en preview. Celle-ci est précisée via l'attribut feature de type jdk.internal.javac.PreviewFeature.Feature qui est une énumération dont les valeurs évoluent à chaque version du JDK.
Cette annotation interne est exploitée dans le compilateur javac notamment pour afficher un avertissement lors de l'utilisation d'une telle fonctionnalité.
Exemple : |
C:\java>javac --enable-preview -source 14 -Xlint:preview HelloBlocTexte.java
HelloBlocTexte.java:5: warning: [preview] text blocks are a preview feature and may be
removed in a future release.
System.out.println("""
^
1 warning
Les éléments d'une API en preview doivent utiliser une balise @since dans le commentaire javadoc de l'élément, indiquant la version à laquelle l'annotation @preview a été ajoutée pour la première fois. Si l'élément de l'API est finalement rendu définitive et permanente dans Java SE version N, la balise @since doit être modifiée pour indiquer la version N.
La plupart des API en preview seront autonomes, sans lien avec les fonctionnalités du langage ou de la VM en preview.
Il arrive parfois qu'une API soit liée à une fonctionnalité en preview. Certaines API en preview peuvent donc être codéveloppées avec des fonctionnalités du langage ou de la VM en preview. Il existe trois types d'API en preview codéveloppées :
Une API en preview réflective doit annotée sa déclaration avec @jdk.internal.javac.PreviewFeature(..., reflective=true). Cela entraîne l'ajout par l'outil javadoc d'un message différent à l'élément que lorsque reflective=false.
La JSR d'un JDK fournit une liste définitive des éléments d'API en preview de la version.
La javadoc propose une page nommée preview-list qui énumère tous les éléments du JDK proposés en preview.
Comme les fonctionnalités en preview n'ont pas atteint un statut final et permanent dans la plate-forme Java SE, elles ne sont pas disponibles par défaut ni à la compilation ni à l'exécution. Pour pouvoir les utiliser, il faut explicitement les activer à la compilation et à l'exécution pour s'assurer qu'elles sont utilisées en âme et conscience.
Les fonctionnalités en preview sont spécifiques à une version particulière de Java SE. Elles nécessitent l'utilisation d'options spéciales à la compilation et à l'exécution.
Important : il faut obligatoirement que le compilateur et la JVM utilisée pour du code utilisant des fonctionnalités en preview soit de la même version du JDK. |
Il est important de ne pas oublier que les fonctionnalités en preview sont susceptibles d'être modifiées et qu'elles sont essentiellement destinées à être testées pour fournir des retours.
Il ne faut pas distribuer d'artefacts qui nécessitent des fonctionnalités non finales : il ne faut pas diffuser de code compilé qui utilise des fonctionnalités en preview. Ces fonctionnalités ne sont compatibles qu'avec la version de Java pour laquelle elles ont été compilées.
Les fonctionnalités en preview sont désactivées par défaut : pour utiliser des fonctionnalités en preview, il faut explicitement les activer. L'activation des fonctionnalités en preview se fait en utilisant l'option --enable-preview lors de l'utilisation des commandes java, javac, javadoc et shell du JDK. Il faut obligatoirement utiliser l'option --enable-preview avec ces outils du JDK pour leur permettre d'utiliser des fonctionnalités en preview.
Les fonctionnalités en preview ne peuvent pas être activées individuellement : l'option --enable-preview active toutes les fonctionnalités en preview pour le JDK utilisé.
Avec Maven, il faut ajouter des options aux plug-ins Compiler, Surefire et Failsafe.
Exemple : |
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>13</release>
<compilerArgs>
--enable-preview
</compilerArgs>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>--enable-preview</argLine>
</configuration>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<argLine>--enable-preview</argLine>
</configuration>
</plugin>
Avec Gradle, il faut ajouter des options au compilateur et à l'exécution des tests.
Exemple : |
compileJava {
options.compilerArgs += ["--enable-preview"]
}
test {
jvmArgs '--enable-preview'
}
Avec IntelliJ IDEA, il faut aller dans « Project Settings / Project » et utiliser la liste déroulante de « Language level » pour sélectionner la version du code source du projet. Pour utiliser une version dans laquelle les fonctionnalités sont utilisables, il faut sélectionner la version suivie de « (Preview) »
Avec Eclipse, dans « Java compiler » des propriétés du projet, il faut cocher « Enable preview features ».
Lors de la compilation avec les fonctionnalités en preview désactivées, une erreur de compilation est émise si le code utilise une fonctionnalité du langage ou une API en preview. Une erreur est justifiée parce qu'une version ultérieure pourrait supprimer la fonctionnalité ou modifier son comportement de manière incompatible.
Exemple : |
C:\java>javac MonApp.java
MonApp.java:5: error: text blocks are a preview feature and are disabled by default.
String message = """
^
(use --enable-preview to enable text blocks)
1 error
Pour compiler du code source avec javac qui utilise les fonctionnalités en preview de la version N du JDK, il faut utiliser la commande javac de la version N du JDK avec l'option de ligne de commande --enable-preview en conjonction avec l'option de ligne de commande --release N ou -source N.
Lors de la compilation du code source, le compilateur considère par défaut que la version du code source est la même que la sienne. Par exemple, en compilant avec un compilateur javac d'un jdk 11, il permettra d'utiliser les fonctionnalités du langage en Java 11.
Il est possible de définir une version du code source différente de celle du compilateur en utilisant les options -source ou --release. L'option --release introduite en Java 9, combine en une seule option -source, -target et -bootclasspath.
L'utilisation des options -source ou --release est facultative pour compiler du code n'utilisant pas de fonctionnalité en preview mais l'une ou l'autre est obligatoire pour compiler du code utilisant des fonctionnalités en preview.
Exemple : |
C:\java>javac --enable-preview TestTextBlock.java
error: --enable-preview must be used with either -source or --release
La version précisée doit obligatoirement correspondre à la version du JDK contenant le compilateur utilisé.
Exemple : |
C:\java>javac -version
javac 13
C:\java>javac --enable-preview --release 12 TestTextBlock.java
error: invalid source release 12 with --enable-preview
(preview language features are only supported for release 13)
Cela oblige le développeur à indiquer explicitement que les fonctionnalités en preview utilisées dans le code correspondent à la version indiquée et cela force le compilateur à vérifier. La signification de l'option --enable-preview change d'un JDK à l'autre. Demander au développeur d'indiquer un numéro de version concret avec --release ou -source, c'est s'attendre à ce que le code qui s'appuie sur les fonctionnalités en preview du JDK N soient liés à cette version et ne puissent pas être compilées avec un JDK N+1.
Seul un compilateur du JDK version N peut prendre en charge les fonctionnalités en preview définies par Java SE version N. On ne peut pas s'attendre à ce qu'un compilateur dans le JDK version N+1 prenne en charge les fonctionnalités en preview définies par Java SE version N, car elles peuvent avoir été modifiées ou abandonnées depuis Java SE version N.
Le compilateur javac d'un JDK version N ne compile pas du code source qui utilise des fonctionnalités du langage en preview ou des API en preview d'autres versions de Java SE, ou pour compiler du code source par rapport à des fichiers .class qui dépendent de fonctionnalités de la VM en preview d'autres versions de Java SE.
Si le code se compile correctement, le compilateur javac d'un JDK N affiche un message s'il détecte l'utilisation d'une fonctionnalité du langage en preview de Java SE N dans le code source. Ce message ne peut pas être désactivé en utilisant @SuppressWarnings dans le code source, parce que les développeurs doivent être conscients de leur dépendance à une fonctionnalité du langage en preview de Java SE N. Cette fonctionnalité peut être changée ou être retirée dans la version N+1 de Java SE.
Lors de la compilation avec les fonctionnalités en preview activées, la JLS à partir de Java 15 recommande fortement que le compilateur affiche un message d'avertissement. Le compilateur javac le propose sous la forme d'une note.
Exemple : |
C:\java>javac --enable-preview --release 19 MonApp.java
Note: HelloApp.java uses preview features of Java SE 19.
Note: Recompile with -Xlint:preview for details.
Comme indiqué dans la JEP 12, il n'est pas possible de supprimer les messages "use of preview features" du compilateur. Ce message affiché par le compilateur n'est pas un avertissement : il ne peut donc pas être désactivé avec @SuppressWarnings("...").
Lors de la compilation avec des fonctionnalités en preview activées, toute référence dans le code source à un élément d'une API essentielle associé à une fonctionnalité en preview doit émettre un avertissement.
Exemple : |
C:\java>javac --enable-preview -source 14 -Xlint:preview MonApp.java
MonApp.java:5: warning: [preview] text blocks are a preview feature and may be
removed in a future release.
String message = """
^
1 warning
Cet avertissement peut être supprimé avec l'annotation @SuppressWarnings("preview"), contrairement à la note affichée par le compilateur javac lorsqu'il détecte l'utilisation d'une fonctionnalité en preview dans le code source.
Lors de la compilation, si le code utilise une fonctionnalité du langage en preview ou une API en preview alors le compilateur ajoute des informations dans le fichier .class généré : la major_version correspond à la version du byte code pour le JDK utilisé et la minor_version dont les 16 bits sont définis avec la valeur 65535.
A l'exécution, il faut aussi utiliser l'option --enable-preview de la JVM pour exécuter du bytecode qui contient des fonctionnalités en preview. Pour exécuter une application qui utilise des fonctionnalités en preview de la version N du JDK, il faut utiliser la commande java du JDK N avec l'option --enable-preview.
Exemple : |
C:\java>java --enable-preview TestTextBlock
Si ce n'est pas le cas, la JVM s'arrête à avec une exception
Exemple : |
C:\java>java TestTextBlock
Error: LinkageError occurred while loading main class TestTextBlock
java.lang.UnsupportedClassVersionError: Preview features are not enabled for
TestTextBlock (class file version 57.65535). Try running with '--enable-preview'
Il en est de même lors de l'exécution directe d'un unique fichier .java à la JVM depuis Java 11. Si le code utilise une fonctionnalité en preview sans l'option --enable-preview.
Exemple : |
C:\java>java TestTextBlock.java
TestTextBlock.java:5: error: text blocks are a preview feature and are disabled by default.
String message = """
^
(use --enable-preview to enable text blocks)
1 error
error: compilation failed
L'option --enable-preview de la JVM permet d'activer les fonctionnalités en preview de la version de Java correspond au JDK utilisé. Cela permet l'exécution de tout fichier .class qui dépend de ces fonctionnalités en preview, soit parce qu'il repose directement sur les fonctionnalités de la VM en preview, soit parce qu'il a été compilé à partir d'un code source qui utilise les fonctionnalités du langage en preview ou des API en preview.
La JVM ne permet pas de charger une classe utilisant une fonctionnalité en preview compilée avec une version différente du JDK de la JVM.
L'option -Xlog:class+preview de la JVM permet d'afficher les classes chargées qui dépendent de fonctionnalités en preview.
Exemple : |
C:\java>java --enable-preview -Xlog:class+preview HelloForeign
[0.073s][info][class,preview] Loading class HelloForeign that depends on
preview features (class file version 63.65535)
Lors de l'utilisation de fonctionnalités en preview, il faut utiliser une JVM de la même version majeure de Java que celle du compilateur utilisé.
Exemple : |
C:\java>javac -version
javac 13
C:\java>javac --enable-preview --release 12 MonApp.java
error: invalid source release 12 with --enable-preview
(preview language features are only supported for release 13)
Il n'est pas possible d'exécuter du bytecode utilisant des fonctionnalités en preview compilé avec une version antérieure. Dans l'exemple ci-dessous, le code est compilé avec un JDK 13 et exécuté avec un JDK 14 sans recompilation. Comme le code utilise des fonctionnalités en preview, une erreur est émise à l'exécution.
Exemple : |
C:\java>java -version
openjdk version "14" 2020-03-17
OpenJDK Runtime Environment AdoptOpenJDK (build 14+36)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 14+36, mixed mode, sharing)
C:\java>java --enable-preview MonApp
Erreur : LinkageError lors du chargement de la classe principale MonApp
java.lang.UnsupportedClassVersionError: MonApp (class file version 57.65535)
was compiled with preview features that are unsupported. This version of the Java
Runtime only recognizes preview features for class file version 58.65535
Cela permet d'éviter que des fonctionnalités en preview obsolètes soit utilisées avec une version de Java différente. Lors de la mise à jour à version majeure suivante de Java, il faut incrémenter la valeur de l'option --release ou -source et selon les évolutions de la fonctionnalité en preview :
Ainsi les fonctionnalités en preview obligent à s'assurer que le code est toujours à jour avec la version de Java utilisée.
Exemple avec une classe qui utilise un bloc de texte introduit en Java 13 en tant que fonctionnalité en preview.
Exemple ( code Java 13 ) : |
class HelloBlocTexte {
public static void main(String[] args) {
System.out.println("""
Hello World""");
}
}
Le code se compile avec un compilateur javac d'un JDK 13 avec les options requises dont un niveau de compatibilité du code source fixé à 13 et peut être exécuté qu'avec une JVM d'un JDK 13.
Résultat : |
C:\java>javac -version
javac 13
C:\java>javac --enable-preview -source 13 HelloBlocTexte.java
Note: HelloBlocTexte.java uses preview language features.
Note: Recompile with -Xlint:preview for details.
C:\java>java --enable-preview HelloBlocTexte
Hello World
Ce code compilé en Java 13 avec des fonctionnalités en preview ne peut pas être exécuté avec une JVM d'un JDK 14. Une telle exécution lève une exception de type LinkageError.
Exemple : |
C:\java>java -version
openjdk version "14" 2020-03-17
OpenJDK Runtime Environment AdoptOpenJDK (build 14+36)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 14+36, mixed mode, sharing)
C:\java>java --enable-preview HelloBlocTexte
Erreur : LinkageError lors du chargement de la classe principale HelloBlocTexte
java.lang.UnsupportedClassVersionError: HelloBlocTexte (class file version 57.65535)
was compiled with preview features that are unsupported. This version of the Java Runtime
only recognizes preview features for class file version 58.65535
Il faut recompiler le code avec un compilateur javac d'un JDK 14. Une erreur de compilation est émise lors de la compilation de code utilisant une fonctionnalité en preview avec un niveau de compatibilité du code source différent de celui du JDK utilisé.
Exemple : |
C:\java>javac --enable-preview -source 13 HelloBlocTexte.java
error: invalid source release 13 with --enable-preview
(preview language features are only supported for release 14)
Il faut recompiler le code avec un compilateur javac du JDK 14 et un niveau de compatibilité du code source fixé à 14.
Exemple : |
C:\java>javac --enable-preview -source 14 HelloBlocTexte.java
Note: HelloBlocTexte.java uses preview language features.
Note: Recompile with -Xlint:preview for details.
C:\java>java --enable-preview HelloBlocTexte
Hello World
La JEP 11 introduit la notion de modules en incubation pour permettre l'inclusion d'API et d'outils du JDK qui pourraient dans le futur, après améliorations et stabilisations, être inclus et pris en charge de manière standard dans la plate-forme Java SE ou dans le JDK.
Le premier module proposé en incubation a été l'API Client HTTP en Java 9 (JEP 110) et 10. Le module est devenu standard en Java 11 (JEP 321).
Java 14 a introduit en incubation l'outil de packaging jpackage (JEP 343) et l'API Foreign Memory Accesss (JEP 370)
Java 14 a introduit en incubation les API Vector (JEP 338) et Foreign Linker (JEP 389)
Java 17 a introduit en incubation l'API Foreign Function and Memory (JEP 412) qui est la fusion des API Foreign Linker et Foreign Memory Access.
Java 19 a introduit en incubation l'API Structured concurrency (JEP 428)
Module/version du JDK |
9 | 10 | 14 | 15 | 16 | 17 | 18 |
jdk.incubator.httpclient | X | X | |||||
jdk.incubator.jpackage |
X | X | |||||
jdk.incubator.foreign |
X | X | X | X | X |
Module/version du JDK | 16 | 17 | 18 | 19 | 20 | 21 | |
jdk.incubator.vector | X | X | X | X | X | X | |
jdk.incubator.concurrent | X | X | X |
Les modules en incubation sont protégés contre une utilisation accidentelle. Ils sont dans l'espace de nommage jdk.incubator. Ces modules fournis par le JDK ne sont pas accessibles par défaut. Il faut implicitement les ajouter en utilisant l'option --add-modules pour permettre leur résolution par une application même si elle n'utilise que le classpath.
Pour une application qui utilise le module path, il faut ajouter en tant que dépendances les modules en incubation.
Une fonctionnalité expérimentale permet de proposer de nouvelles fonctionnalités non triviales dans la JVM Hotspot. Il n'y a pas de JEP qui décrivent comment sont mises en oeuvre les fonctionnalités expérimentales : les fonctionnalités expérimentales sont plus une convention qu'un processus formel.
Un exemple de fonctionnalités expérimentales est le ramasse-miettes ZGC, introduit en Java 11 uniquement sur Linux x64 (JEP 333). Après un portage sous Windows et Mac OSX et plusieurs améliorations, ZGC a été promu standard en Java 15 (JEP 377).
Les fonctions expérimentales sont des fonctionnalités de la JVM Hotspot qui sont désactivées par défaut. Pour pouvoir utiliser de telles fonctionnalités, il faut utiliser l'option -XX:+UnlockExperimentalVMOptions pour autoriser les fonctions expérimentales. Il est alors possible d'utiliser l'option de la fonctionnalité expérimentale.
Pour certaines fonctionnalités très impactantes de certains projets d'OpenJDK de longues durées, des builds early access (EA) sont proposés. Ces projets mènent des recherches et des investigations dans des domaines particuliers afin d'améliorer certains aspects de la plate-forme Java.
Ils peuvent être livrés de manière itérative et incrémentale pour proposer différents prototypes et expérimentations de solutions potentielles qui pourront être améliorées ou abandonnées.
Le public cible de ces builds EA est composé d'utilisateurs avertis souhaitant tester les fonctionnalités proposées et fournir des retours et des commentaires sur leurs expérimentations. Ces builds ne sont soumis à aucune règle de compatibilité.
Une fois qu'une fonctionnalité donnée a atteint le niveau de stabilité et de qualité attendu, elle peut alors être proposée selon les mécanismes habituels, tels qu'une JEP.
Les builds early access du JDK sont téléchargeables à l'url : jdk.java.net.
Développons en Java v 2.40 Copyright (C) 1999-2023 Jean-Michel DOUDOUX. |