Exécution de programmes à fichier unique dans Java 11 sans compilation



Laissez le fichier source HelloUniverse.java contenir une définition de classe et une méthode main statique qui génère une seule ligne de texte vers le terminal:

 public class HelloUniverse{ public static void main(String[] args) { System.out.println("Hello InfoQ Universe"); } } 

En règle générale, pour exécuter cette classe, vous devez d'abord la compiler à l'aide du compilateur Java (javac), qui crée le fichier HelloUniverse.class:

 mohamed_taman$ javac HelloUniverse.java 

Ensuite, vous devez exécuter le fichier résultant à l'aide de la commande Java virtual machine (interprète):

 mohamed_taman$ java HelloUniverse Hello InfoQ Universe 

Ensuite, virtualka démarrera en premier, ce qui chargera la classe et exécutera le code.

Et si vous avez besoin de vérifier rapidement un morceau de code? Ou êtes-vous nouveau à Java ( dans ce cas, un point clé ) et souhaitez expérimenter avec le langage? Les deux étapes décrites peuvent compliquer les choses.

Dans Java SE 11, vous pouvez exécuter directement des fichiers source uniques sans compilation intermédiaire.

Cette fonctionnalité est particulièrement utile pour les débutants qui souhaitent travailler avec des programmes simples. Combiné avec jshell, vous obtenez un excellent ensemble d'outils pour éduquer les débutants.

Les professionnels peuvent utiliser ces outils pour apprendre des innovations dans le langage ou tester des API inconnues. À notre avis, il est préférable d'automatiser de nombreuses tâches, telles que l'écriture de programmes Java sous forme de scripts avec une exécution ultérieure à partir du shell du système d'exploitation. En conséquence, nous pouvons travailler de manière flexible avec des scripts shell et utiliser toutes les fonctionnalités de Java. Parlons de cela plus en détail dans la deuxième partie de l'article.

Cette grande fonctionnalité de Java 11 vous permet d'exécuter directement un seul fichier source sans compilation. Discutons.

De quoi avez-vous besoin


Pour exécuter le code fourni dans l'article, vous avez besoin d'une version Java inférieure à 11. Au moment de la rédaction, la version actuelle était Java SE Development Kit 12.0.1 - la version finale est ici , acceptez simplement les termes de la licence et cliquez sur le lien pour votre système d'exploitation. Si vous souhaitez tester les dernières fonctionnalités, vous pouvez télécharger un accès anticipé au JDK 13.

Veuillez noter que les versions de divers fournisseurs OpenJDK sont désormais également disponibles, y compris AdoptOpenJDK .

Dans cet article, nous utiliserons un Ă©diteur de texte brut au lieu de Java IDE pour Ă©viter toute la magie de l'IDE, et utiliserons la ligne de commande Java directement dans le terminal.

Exécutez .java avec Java


La fonction JEP 330 (exécutant des programmes à fichier unique avec du code source) est apparue dans JDK 11. Elle vous permet d'exécuter directement des fichiers source avec du code source Java, sans utiliser d'interpréteur. Le code source est compilé en mémoire puis exécuté par l'interpréteur sans créer de fichier .class sur le disque.

Cependant, cette fonction est limitée au code stocké dans un seul fichier. Vous ne pouvez pas exécuter plusieurs fichiers source à la fois.

Pour contourner cette limitation, toutes les classes doivent être définies dans un seul fichier. Il n'y a aucune restriction sur leur nombre. De plus, bien qu'ils soient dans le même dossier, peu importe qu'ils soient publics ou privés.

La première classe définie dans le fichier sera considérée comme la principale, et la méthode principale doit y être placée. Autrement dit, l'ordre est important.

Premier exemple


Commençons par l'exemple le plus simple classique - Hello Universe!

Nous allons démontrer la fonctionnalité décrite avec divers exemples afin que vous ayez une idée de la façon dont elle peut être utilisée dans la programmation quotidienne.

Créez un fichier HelloUniverse.java avec le code du début de l'article, compilez et exécutez le fichier de classe résultant. Ensuite, supprimez-le, vous comprendrez maintenant pourquoi:

 mohamed_taman$ rm HelloUniverse.class 

Si vous utilisez maintenant l'interpréteur Java, vous exécutez le fichier de classe sans compilation:

 mohamed_taman$ java HelloUniverse.java Hello InfoQ Universe 

vous verrez le même résultat: le fichier sera exécuté.

Cela signifie que vous pouvez maintenant simplement exécuter java HelloUniverse.java . Nous transférons le code source lui-même, et non le fichier de classe: le système à l'intérieur de lui-même le compile, lance et affiche un message dans la console.

Autrement dit, la compilation est toujours effectuée sous le capot. Et en cas d'erreur, nous recevrons une notification à ce sujet. Vous pouvez vérifier la structure du répertoire et vous assurer que le fichier de classe n'est pas généré, la compilation est effectuée en mémoire.

Voyons maintenant comment tout cela fonctionne.

Comment l'interpréteur Java exécute le programme HelloUniverse


Dans JDK 10, le lanceur Java peut fonctionner en trois modes:

  1. Exécution du fichier de classe.
  2. Exécution de la classe principale à partir d'un fichier JAR.
  3. Exécution de la classe principale du module.

Et en Java 11, un quatrième mode est apparu:

  1. Exécution de la classe déclarée dans le fichier source.

Dans ce mode, le fichier source est compilé en mémoire, puis la première classe de ce fichier est exécutée.

Le système détermine votre intention de saisir le fichier source selon deux critères:

  1. Le premier élément de la ligne de commande n'est ni une option ni une partie d'une option.
  2. La ligne peut contenir l' --source <vrsion> .

Dans le premier cas, Java cherchera d'abord si le premier élément de la commande est une option ou une partie de celle-ci. S'il s'agit d'un nom de fichier se terminant par .java, le système le considérera comme le code source qui doit être compilé et exécuté. Vous pouvez également ajouter des options à la commande Java avant le nom du fichier source. Par exemple, si vous souhaitez définir le chemin d'accès aux classes lorsque le fichier source utilise des dépendances externes.

Dans le second cas, le mode de travail avec le fichier source est sélectionné et le premier élément de la ligne de commande, qui n'est pas une option, est considéré comme le fichier source qui doit être compilé et exécuté.

Si le fichier n'a pas l'extension .java, vous devez utiliser l'option --source pour le forcer Ă  passer en mode de travail avec le fichier source.

Ceci est important dans les cas où le fichier source est un «script» qui doit être exécuté et où le nom de fichier n'est pas conforme aux conventions habituelles pour nommer les fichiers source avec du code Java.

En utilisant l'option --source , --source pouvez déterminer la version de la langue source. Nous en parlerons ci-dessous.

Puis-je transmettre des arguments sur la ligne de commande?


DĂ©veloppons notre programme Hello Universe afin qu'il affiche un message d'accueil personnel Ă  tout utilisateur qui visite l'Univers InfoQ:

 public class HelloUniverse2{ public static void main(String[] args){ if ( args == null || args.length< 1 ){ System.err.println("Name required"); System.exit(1); } var name = args[0]; System.out.printf("Hello, %s to InfoQ Universe!! %n", name); } } 

Enregistrez le code dans le fichier Greater.java. Notez que le nom de fichier ne correspond pas au nom de la classe publique. Cela viole les règles de la spécification Java.

Exécutez le code:

 mohamed_taman$ java Greater.java "Mo. Taman" Hello, Mo. Taman to InfoQ universe!! 

Comme vous pouvez le voir, peu importe que les noms de classe et de fichier ne correspondent pas. Un lecteur attentif peut également remarquer que nous avons transmis des arguments au code après avoir traité le nom du fichier. Cela signifie que tout argument sur la ligne de commande suivant le nom de fichier est transmis à la méthode principale standard.

DĂ©terminer le niveau de code source Ă  l'aide de l'option --source


Il existe deux scénarios d'utilisation de l'option --source :

  1. DĂ©terminer le niveau de code source.
  2. Forcer le runtime Java en mode source.

Dans le premier cas, si vous n'avez pas spécifié le niveau de code source, la version actuelle du JDK est prise pour cela. Et dans le second cas, les fichiers avec des extensions autres que .java peuvent être transférés pour compilation et exécution à la volée.

Regardons d'abord le deuxième scénario. Renommez Greater.java simplement en supérieur sans extension et essayez d'exécuter:

 mohamed_taman$ java greater "Mo. Taman" Error: Could not find or load main class greater Caused by: java.lang.ClassNotFoundException: greater 

En l'absence de l'extension .java, l'interpréteur de commandes recherche la classe compilée par le nom passé en argument - c'est le premier mode de fonctionnement du lanceur Java. Pour éviter cela, utilisez l'option --source pour forcer le basculement vers le mode fichier source:

 mohamed_taman$ java --source 11 greater "Mo. Taman" Hello, Mo. Taman to InfoQ universe!! 

Passons maintenant au premier scénario. La classe Greater.java est compatible avec JDK 10 car elle contient le mot clé var , mais n'est pas compatible avec JDK 9. Changez la source en 10 :

 mohamed_taman$ java --source 10 Greater.java "Mo. Taman" Hello Mo. Taman to InfoQ universe!! 

Exécutez à nouveau la commande précédente, mais cette fois, passez --source 9 au lieu de 10 :

 mohamed_taman$ java --source 9 Greater.java "Mo. Taman" Greater.java:8: warning: as of release 10, 'var' is a restricted local variable type and cannot be used for type declarations or as the element type of an array var name = args[0]; ^ Greater.java:8: error: cannot find symbol var name = args[0]; ^ symbol: class var location: class HelloWorld 1 error 1 warning error: compilation failed 

Remarque: le compilateur avertit que var est devenu un nom de type restreint dans JDK 10. Mais comme nous avons un langage de niveau 10, la compilation continue. Cependant, un plantage se produit car le fichier source n'a pas de type nommé var .

Tout est simple. Considérez maintenant l'utilisation de plusieurs classes.

Cette approche fonctionne-t-elle avec plusieurs classes?


Oui.

Prenons un exemple avec deux classes. Le code vérifie si la valeur de chaîne donnée est un palindrome .

Voici le code enregistré dans le fichier PalindromeChecker.java:

 import static java.lang.System.*; public class PalindromeChecker { public static void main(String[] args) { if ( args == null || args.length< 1 ){ err.println("String is required!!"); exit(1); } out.printf("The string {%s} is a Palindrome!! %b %n", args[0], StringUtils .isPalindrome(args[0])); } } public class StringUtils { public static Boolean isPalindrome(String word) { return (new StringBuilder(word)) .reverse() .toString() .equalsIgnoreCase(word); } } 

Exécutez le fichier:

 mohamed_taman:code$ java PalindromeChecker.java RediVidEr The string {RediVidEr} is a Palindrome!! True 

Exécutez-le à nouveau, en remplaçant «RaceCar» au lieu de «MadAm»:

 mohamed_taman:code$ java PalindromeChecker.java RaceCar The string {RaceCar} is a Palindrome!! True 

Remplacez maintenant "Mohamed" au lieu de "RaceCar":

 mohamed_taman:code$ java PalindromeChecker.java Taman The string {Taman} is a Palindrome!! false 

Comme vous pouvez le voir, vous pouvez ajouter autant de classes publiques que vous le souhaitez à un fichier source. Assurez-vous que la méthode principale est définie en premier. L'interpréteur utilisera la première classe comme point de départ pour démarrer le programme après avoir compilé le code en mémoire.

Puis-je utiliser des modules?


Oui, pas de limites. Le code compilé en mémoire s'exécute dans le cadre d'un module sans nom avec l' --add-modules=ALL-DEFAULT , qui donne accès à tous les modules fournis avec le JDK.

Autrement dit, le code peut utiliser différents modules sans avoir à définir explicitement les dépendances à l'aide de module-info.java.

Examinons le code qui effectue un appel HTTP à l'aide de la nouvelle API client HTTP, introduite dans JDK 11. Notez que ces API ont été introduites dans Java SE 9 en tant que fonctionnalité expérimentale, mais elles ont maintenant le statut d'une fonction à part entière du module java.net.http .

Dans cet exemple, nous appellerons une API REST simple en utilisant la méthode GET pour obtenir une liste d'utilisateurs. Nous nous tournons vers le service public reqres.in/api/users?page=2 . Nous enregistrons le code dans un fichier appelé UsersHttpClient.java:

 import static java.lang.System.*; import java.net.http.*; import java.net.http.HttpResponse.BodyHandlers; import java.net.*; import java.io.IOException; public class UsersHttpClient{ public static void main(String[] args) throws Exception{ var client = HttpClient.newBuilder().build(); var request = HttpRequest.newBuilder() .GET() .uri(URI.create("https://reqres.in/api/users?page=2")) .build(); var response = client.send(request, BodyHandlers.ofString()); out.printf("Response code is: %d %n",response.statusCode()); out.printf("The response body is:%n %s %n", response.body()); } } 

Exécutez le programme et obtenez le résultat:

 mohamed_taman:code$ java UsersHttpClient.java Response code is: 200 The response body is: {"page":2,"per_page":3,"total":12,"total_pages":4,"data":[{"id":4,"first_name":"Eve","last_name":"Holt","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/marcoramires/128.jpg"},{"id":5,"first_name":"Charles","last_name":"Morris","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/stephenmoon/128.jpg"},{"id":6,"first_name":"Tracey","last_name":"Ramos","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/bigmancho/128.jpg"}]} 

Vous pouvez maintenant tester rapidement les nouvelles fonctionnalités fournies par différents modules sans créer votre propre module.

Pourquoi les scripts sont-ils importants en Java?


Rappelons d'abord ce que sont les scripts:

Un script est un programme écrit pour un environnement d'exécution spécifique qui automatise l'exécution de tâches ou de commandes qu'une personne peut exécuter à son tour.

De cette définition générale, nous pouvons dériver une définition simple d'un langage de script - c'est un langage de programmation qui utilise des constructions de haut niveau pour interpréter et exécuter une commande (ou des commandes) à la fois.

Le langage de script utilise une série de commandes écrites dans un fichier. Souvent, ces langages sont interprétés (plutôt que compilés) et adhèrent à un style de programmation procédurale (bien que certains langages de script aient également les propriétés des langages orientés objet).

En général, les langages de script sont plus faciles à apprendre et plus rapides à taper que les langages compilés plus structurés comme Java, C et C ++. Les langages de script côté serveur incluent Perl, PHP et Python, et côté client , JavaScript.

Pendant longtemps, Java a été considéré comme un langage compilé bien structuré et hautement typé qui est interprété par une machine virtuelle pour fonctionner sur n'importe quelle architecture informatique. Cependant, Java n'est pas aussi facile à apprendre et à prototyper que les autres langages de script.

Néanmoins, Java a déjà 24 ans, il est utilisé par environ 10 millions de développeurs à travers le monde. Les versions récentes ont ajouté un certain nombre de nouvelles fonctionnalités pour permettre aux jeunes programmeurs d'apprendre plus facilement ce langage, ainsi que d'utiliser les fonctions du langage et de l'API sans compilation ni IDE. Par exemple, Java SE 9 a introduit l'outil JShell (REPL), qui prend en charge la programmation interactive.

Et avec la sortie de JDK 11, ce langage a eu la possibilité de prendre en charge les scripts, car maintenant vous pouvez exécuter du code avec un simple appel à la commande java !

Il existe deux façons principales d'utiliser des scripts dans Java 11:

  1. Appel direct Ă  la commande java .
  2. Utilisation de scripts * nix pour la ligne de commande, similaire aux scripts Bash.

Nous avons déjà envisagé la première option, nous allons maintenant traiter de la seconde. Cela nous ouvre de nombreuses possibilités.

Fichiers Shebang: exécuter Java en tant que script shell


Ainsi, dans Java SE 11, la prise en charge des scripts est apparue, y compris les fichiers shebang traditionnels du monde * nix. Pour les prendre en charge, aucune spécification de langue n'était requise.

Dans le fichier shebang, les deux premiers octets doivent être 0x23 et 0x21. Il s'agit du codage de caractères ASCII #! .. Tous les octets suivants du fichier sont lus sur la base du système de codage par défaut sur cette plate-forme.

Ainsi, pour que le fichier soit exécuté à l'aide du mécanisme shebang intégré au système d'exploitation, il n'y a qu'une seule exigence: la première ligne commence par #! .. Cela signifie que nous n'avons pas besoin de première ligne spéciale lorsque le lanceur Java est explicitement utilisé pour exécuter le code à partir du fichier source, comme c'est le cas avec HelloUniverse.java.

Exécutez l'exemple suivant dans un terminal exécutant macOS Mojave 10.14.5 . Mais d'abord, nous définirons les règles importantes à suivre lors de la création d'un fichier shebang:

  • Ne mĂ©langez pas le code Java avec le code du langage de script de votre script shell OS.
  • Si vous devez ajouter des options de machine virtuelle, vous devez spĂ©cifier --source première option après le nom du fichier exĂ©cutable dans le fichier shebang. Les options de la machine virtuelle incluent: --class-path , --module-path , --add-exports , --add-modules , --limit-modules , --patch-module , --upgrade-module-path , ainsi que leurs variantes. Cette nouvelle option comprend Ă©galement la nouvelle option --enable-preview , dĂ©crite dans JEP 12 .
  • Vous devez spĂ©cifier la version de Java utilisĂ©e dans le fichier source.
  • La première ligne du fichier doit commencer par des caractères shebang (#!). Par exemple:
    #!/path/to/java --source <vrsion>
  • Pour les fichiers source Java, N'UTILISEZ PAS le mĂ©canisme shebang pour exĂ©cuter des fichiers conformes Ă  la convention de dĂ©nomination standard (fin en .java)
  • Vous devez marquer le fichier comme exĂ©cutable avec la commande:
    chmod +x <Filname>.<Extnsion> .

Créons un fichier shebang (programme de script), qui listera le contenu du répertoire dont le nom sera passé en paramètre. Si aucun paramètre n'est transmis, le répertoire courant sera pris par défaut.

 #!/usr/bin/java --source 11 import java.nio.file.*; import static java.lang.System.*; public class DirectoryLister { public static void main(String[] args) throws Exception { vardirName = "."; if ( args == null || args.length< 1 ){ err.println("Will list the current directory"); } else { dirName = args[0]; } Files .walk(Paths.get(dirName)) .forEach(out::println); } } 

Enregistrez le code dans un fichier appelé dirlist sans l'extension, puis marquez-le comme exécutable: mohamed_taman:code$ chmod +x dirlist .

Exécutez le fichier:

 mohamed_taman:code$ ./dirlist Will list the current directory . ./PalindromeChecker.java ./greater ./UsersHttpClient.java ./HelloWorld.java ./Greater.java ./dirlist 

Exécutez-le à nouveau à l'aide de la commande qui transmet le répertoire parent et vérifiez le résultat.

 mohamed_taman:code$ ./dirlist ../ 

Remarque: lors de l'évaluation du code source, l'interpréteur ignore la ligne shebang (première ligne). Ainsi, le fichier shebang peut être appelé explicitement à l'aide du lanceur, par exemple, avec des options supplémentaires:

 $ java -Dtrace=true --source 11 dirlist 

Il convient également de noter: si le fichier de script se trouve dans le répertoire en cours, vous pouvez l'exécuter comme ceci:

 $ ./dirlist 

Et si le script se trouve dans un répertoire dont le chemin est spécifié dans l'utilisateur PATH, alors vous pouvez l'exécuter comme ceci:

 $ dirlist 

Et enfin, je vais vous donner quelques conseils Ă  garder Ă  l'esprit lorsque vous utilisez des scripts.

Astuces


  1. Certaines options que vous transmettrez Ă  javac peuvent ne pas ĂŞtre transmises (ou non reconnues) Ă  java , par exemple, les options -Werror ou -Werror .
  2. S'il y a des fichiers .class et .java dans le chemin de classe, le lanceur vous forcera Ă  utiliser le fichier de classe.

     mohamed_taman:code$ javac HelloUniverse.java mohamed_taman:code$ java HelloUniverse.java error: class found on application class path: HelloUniverse 

  3. Soyez conscient de la possibilité d'un conflit entre les noms de classe et de package. Jetez un œil à cette structure de répertoires:

     mohamed_taman:code$ tree . ├── Greater.java ├── HelloUniverse │ ├── java.class │ └── java.java ├── HelloUniverse.java ├── PalindromeChecker.java ├── UsersHttpClient.java ├── dirlist └── greater 

    Notez les deux java.java dans le package HelloUniverse et le fichier HelloUniverse.java dans le même répertoire. Si vous essayez d'exécuter:

     mohamed_taman:code$ java HelloUniverse.java 

    alors quel fichier sera exécuté en premier et quel second? Le lanceur ne fait plus référence au fichier de classe dans le package HelloUniverse. Au lieu de cela, il chargera et exécutera le fichier HelloUniverse.java d'origine, c'est-à-dire que le fichier sera lancé dans le répertoire actuel.

Les fichiers Shebang ouvrent de nombreuses possibilités pour créer des scripts pour automatiser toutes sortes de tâches à l'aide d'outils Java.

Résumé


À partir de Java SE 11 et pour la première fois dans l'historique de programmation, vous pouvez exécuter directement des scripts avec du code Java sans compilation. Cela vous permet d'écrire des scripts Java et de les exécuter à partir de la ligne de commande * nix.

Expérimentez avec cette fonctionnalité et partagez vos connaissances avec d'autres.

Sources utiles


Source: https://habr.com/ru/post/fr483888/


All Articles