Ausführen von Programmen mit nur einer Datei in Java 11 ohne Kompilierung



Lassen Sie die Quelldatei HelloUniverse.java eine Klassendefinition und eine statische Hauptmethode enthalten, die eine einzelne Textzeile an das Terminal ausgibt:

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

Um diese Klasse auszuführen, müssen Sie sie normalerweise zuerst mit dem Java-Compiler (javac) kompilieren, der die Datei HelloUniverse.class erstellt:

 mohamed_taman$ javac HelloUniverse.java 

Anschließend müssen Sie die resultierende Datei mit dem Java Virtual Machine-Befehl (Interpreter) ausführen:

 mohamed_taman$ java HelloUniverse Hello InfoQ Universe 

Dann startet zuerst die Virtualka, die die Klasse lädt und den Code ausführt.

Und wenn Sie schnell einen Code überprüfen müssen? Oder sind Sie neu in Java ( in diesem Fall ein wichtiger Punkt ) und möchten mit der Sprache experimentieren? Die beiden beschriebenen Schritte können die Dinge komplizieren.

In Java SE 11 können Sie einzelne Quelldateien ohne Zwischenkompilierung direkt ausführen.

Diese Funktion ist besonders nützlich für Anfänger, die mit einfachen Programmen arbeiten möchten. In Kombination mit jshell erhalten Sie eine Reihe großartiger Tools für die Ausbildung von Anfängern.

Profis können mit diesen Tools Neuerungen in der Sprache erlernen oder unbekannte APIs testen. Unserer Meinung nach ist es besser, viele Aufgaben zu automatisieren, z. B. das Schreiben von Java-Programmen in Form von Skripten mit anschließender Ausführung über die OS-Shell. Dadurch können wir flexibel mit Shell-Skripten arbeiten und alle Funktionen von Java nutzen. Lassen Sie uns im zweiten Teil des Artikels näher darauf eingehen.

Mit dieser großartigen Funktion von Java 11 können Sie eine einzelne Quelldatei direkt ausführen, ohne sie kompilieren zu müssen. Lass uns diskutieren.

Was brauchst du


Zum Ausführen des im Artikel bereitgestellten Codes benötigen Sie eine Java-Version von mindestens 11. Zum Zeitpunkt des Schreibens war die aktuelle Version Java SE Development Kit 12.0.1. Die endgültige Version ist hier . Akzeptieren Sie einfach die Lizenzbedingungen und klicken Sie auf den Link für Ihr Betriebssystem. Wenn Sie mit den neuesten Funktionen experimentieren möchten, können Sie JDK 13 Early Access herunterladen .

Bitte beachten Sie, dass jetzt auch Releases verschiedener OpenJDK-Anbieter verfügbar sind, einschließlich AdoptOpenJDK .

In diesem Artikel verwenden wir anstelle der Java-IDE einen Nur-Text-Editor, um den Zauber der IDE zu umgehen, und verwenden die Java-Befehlszeile direkt im Terminal.

Führen Sie .java mit Java aus


Die JEP 330- Funktion (Ausführen von Einzeldateiprogrammen mit Quellcode) ist in JDK 11 erschienen. Mit ihr können Sie Quelldateien mit Java-Quellcode direkt ausführen, ohne einen Interpreter zu verwenden. Der Quellcode wird im Speicher kompiliert und dann vom Interpreter ausgeführt, ohne dass eine .class-Datei auf der Festplatte erstellt wird.

Diese Funktion ist jedoch auf Code beschränkt, der in einer einzelnen Datei gespeichert ist. Sie können nicht mehrere Quelldateien gleichzeitig ausführen.

Um diese Einschränkung zu umgehen, müssen alle Klassen in einer einzigen Datei definiert werden. Ihre Anzahl unterliegt keinen Beschränkungen. Außerdem spielt es keine Rolle, ob sie öffentlich oder privat sind, während sie sich in derselben Datei befinden.

Die erste in der Datei definierte Klasse wird als die Hauptklasse betrachtet, und die Hauptmethode muss darin platziert werden. Ordnung ist also wichtig.

Erstes Beispiel


Beginnen wir mit dem klassisch einfachsten Beispiel - Hallo Universum!

Wir werden das beschriebene Feature anhand verschiedener Beispiele demonstrieren, damit Sie einen Eindruck davon bekommen, wie es in der täglichen Programmierung eingesetzt werden kann.

Erstellen Sie eine HelloUniverse.java-Datei mit dem Code vom Anfang des Artikels, kompilieren Sie die resultierende Klassendatei und führen Sie sie aus. Dann löschen Sie es, jetzt werden Sie verstehen, warum:

 mohamed_taman$ rm HelloUniverse.class 

Wenn Sie jetzt den Java-Interpreter verwenden, führen Sie die Klassendatei ohne Kompilierung aus:

 mohamed_taman$ java HelloUniverse.java Hello InfoQ Universe 

Sie sehen das gleiche Ergebnis: Die Datei wird ausgeführt.

Dies bedeutet, dass Sie jetzt nur java HelloUniverse.java ausführen java HelloUniverse.java . Wir übertragen den Quellcode selbst und nicht die Klassendatei: Das System in sich kompiliert ihn, startet und zeigt eine Meldung in der Konsole an.

Das heißt, die Kompilierung wird immer noch unter der Haube durchgeführt. Und im Falle ihres Fehlers werden wir darüber benachrichtigt. Sie können die Verzeichnisstruktur überprüfen und sicherstellen, dass die Klassendatei nicht generiert wird und die Kompilierung im Speicher ausgeführt wird.

Nun wollen wir herausfinden, wie alles funktioniert.

Wie der Java-Interpreter das HelloUniverse-Programm ausführt


In JDK 10 kann der Java-Launcher in drei Modi ausgeführt werden:

  1. Ausführung der Klassendatei.
  2. Ausführung der Hauptklasse aus einer JAR-Datei.
  3. Ausführung der Hauptklasse des Moduls.

Und in Java 11 erschien ein vierter Modus:

  1. Ausführung der in der Quelldatei deklarierten Klasse.

In diesem Modus wird die Quelldatei im Speicher kompiliert und anschließend die erste Klasse aus dieser Datei ausgeführt.

Das System bestimmt Ihre Absicht, die Quelldatei einzugeben, anhand von zwei Kriterien:

  1. Das erste Element in der Befehlszeile ist weder eine Option noch Teil einer Option.
  2. Die Zeile kann die Option --source <vrsion> .

Im ersten Fall wird Java zuerst herausfinden, ob das erste Element des Befehls eine Option oder ein Teil davon ist. Wenn dies ein Dateiname ist, der auf .java endet, betrachtet das System ihn als den Quellcode, der kompiliert und ausgeführt werden muss. Sie können dem Java-Befehl auch Optionen vor dem Namen der Quelldatei hinzufügen. Zum Beispiel, wenn Sie den Klassenpfad festlegen möchten, wenn die Quelldatei externe Abhängigkeiten verwendet.

Im zweiten Fall wird der Modus zum Arbeiten mit der Quelldatei ausgewählt und das erste Element in der Befehlszeile, das keine Option ist, wird als Quelldatei betrachtet, die kompiliert und ausgeführt werden muss.

Wenn die Datei nicht die Erweiterung .java aufweist, müssen Sie die Option --source , um den --source in den Arbeitsmodus mit der Quelldatei zu erzwingen.

Dies ist in Fällen wichtig, in denen die Quelldatei ein „Skript“ ist, das ausgeführt werden muss, und der Dateiname nicht den üblichen Konventionen für die Benennung der Quelldateien mit Java-Code entspricht.

Mit der Option --source können --source die Version der Quellsprache ermitteln. Wir werden weiter unten darauf eingehen.

Kann ich in der Befehlszeile Argumente übergeben?


Erweitern wir unser Hello Universe-Programm so, dass jedem Benutzer, der das InfoQ Universe besucht, eine persönliche Begrüßung angezeigt wird:

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

Speichern Sie den Code in der Datei Greater.java. Beachten Sie, dass der Dateiname nicht mit dem Namen der öffentlichen Klasse übereinstimmt. Dies verstößt gegen die Regeln der Java-Spezifikation.

Führen Sie den Code aus:

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

Wie Sie sehen, spielt es keine Rolle, dass die Klassen- und Dateinamen nicht übereinstimmen. Ein aufmerksamer Leser kann auch feststellen, dass wir nach der Verarbeitung des Dateinamens Argumente an den Code übergeben haben. Dies bedeutet, dass alle Argumente in der Befehlszeile, die auf den Dateinamen folgen, an die Standard-Hauptmethode übergeben werden.

Bestimmen Sie die Ebene des Quellcodes mit der Option --source


Es gibt zwei Szenarien für die Verwendung der Option --source :

  1. Bestimmen der Ebene des Quellcodes.
  2. Java-Laufzeit in den Quellmodus versetzen.

Wenn Sie im ersten Fall die Quellcode-Ebene nicht angegeben haben, wird die aktuelle Version des JDK dafür verwendet. Im zweiten Fall können Dateien mit anderen Erweiterungen als .java direkt zum Kompilieren und Ausführen übertragen werden.

Schauen wir uns zuerst das zweite Szenario an. Benennen Sie Greater.java einfach in Greater ohne Erweiterung um und versuchen Sie Folgendes auszuführen:

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

In Abwesenheit der Erweiterung .java sucht der Befehlsinterpreter nach der kompilierten Klasse anhand des als Argument übergebenen Namens. Dies ist die erste Betriebsart des Java-Launchers. Um dies zu verhindern, verwenden Sie die Option --source , um den Wechsel in den Quelldateimodus zu erzwingen:

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

Fahren wir nun mit dem ersten Szenario fort. Die Greater.java-Klasse ist mit JDK 10 kompatibel, da sie das Schlüsselwort var enthält, jedoch nicht mit JDK 9 kompatibel ist. Ändern Sie die source in 10 :

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

Führen Sie den vorherigen Befehl erneut aus, aber übergeben Sie diesmal --source 9 anstelle von 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 

Hinweis: Der Compiler warnt, dass var in JDK 10 ein eingeschränkter Typname geworden ist. Da wir jedoch eine Sprache der Stufe 10 haben, wird die Kompilierung fortgesetzt. Es kommt jedoch zu einem Absturz, da die Quelldatei keinen Typ namens var .

Alles ist einfach. Betrachten Sie nun die Verwendung mehrerer Klassen.

Funktioniert dieser Ansatz mit mehreren Klassen?


Ja, das tut es.

Betrachten Sie ein Beispiel mit zwei Klassen. Der Code überprüft, ob der angegebene Zeichenfolgenwert ein Palindrom ist .

Hier ist der Code, der in der Datei PalindromeChecker.java gespeichert ist:

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

Führen Sie die Datei aus:

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

Führen Sie es erneut aus und ersetzen Sie "RaceCar" anstelle von "MadAm":

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

Ersetze jetzt "Mohamed" anstelle von "RaceCar":

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

Wie Sie sehen, können Sie einer Quelldatei beliebig viele öffentliche Klassen hinzufügen. Stellen Sie sicher, dass zuerst die Hauptmethode definiert wird. Der Interpreter verwendet die erste Klasse als Startpunkt für das Starten des Programms nach dem Kompilieren des Codes im Speicher.

Kann ich Module verwenden?


Ja, keine Grenzen. Der speicherkompilierte Code wird als Teil eines unbenannten Moduls mit der --add-modules=ALL-DEFAULT , mit der auf alle mit dem JDK --add-modules=ALL-DEFAULT .

Das heißt, der Code kann verschiedene Module verwenden, ohne dass Abhängigkeiten explizit mit module-info.java definiert werden müssen.

Schauen wir uns den Code an, der einen HTTP-Aufruf mit der in JDK 11 eingeführten neuen HTTP-Client-API ausführt. Beachten Sie, dass diese APIs in Java SE 9 als experimentelles Feature eingeführt wurden, aber jetzt den Status einer vollwertigen Funktion des Moduls java.net.http haben .

In diesem Beispiel rufen wir eine einfache REST-API mit der GET-Methode auf, um eine Liste der Benutzer abzurufen. Wir wenden uns an den öffentlichen Dienst reqres.in/api/users?page=2 . Wir speichern den Code in einer Datei namens 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()); } } 

Führen Sie das Programm aus und erhalten Sie das Ergebnis:

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

Jetzt können Sie schnell neue Funktionen testen, die von verschiedenen Modulen bereitgestellt werden, ohne ein eigenes Modul erstellen zu müssen.

Warum sind Skripte in Java wichtig?


Erinnern wir uns zunächst an die folgenden Skripte:

Ein Skript ist ein Programm, das für eine bestimmte Laufzeitumgebung geschrieben wurde und das die Ausführung von Aufgaben oder Befehlen automatisiert, die eine Person nacheinander ausführen kann.

Aus dieser allgemeinen Definition können wir eine einfache Definition einer Skriptsprache ableiten - es ist eine Programmiersprache, die übergeordnete Konstrukte verwendet, um einen Befehl (oder mehrere Befehle) zu interpretieren und auszuführen.

Die Skriptsprache verwendet eine Reihe von Befehlen, die in einer Datei geschrieben sind. Oft werden diese Sprachen interpretiert (anstatt kompiliert) und folgen einem prozeduralen Programmierstil (obwohl einige Skriptsprachen auch die Eigenschaften objektorientierter Sprachen haben).

Im Allgemeinen sind Skriptsprachen leichter zu lernen und schneller einzugeben als strukturiertere kompilierte Sprachen wie Java, C und C ++. Serverseitige Skriptsprachen umfassen Perl, PHP und Python sowie clientseitig JavaScript.

Java galt lange Zeit als eine gut strukturierte, stark typisierte kompilierte Sprache, die von einer virtuellen Maschine interpretiert wird, um auf jeder Computerarchitektur ausgeführt zu werden. Java ist jedoch im Vergleich zu anderen Skriptsprachen nicht so einfach zu erlernen und zu prototypisieren.

Dennoch ist Java bereits 24 Jahre alt geworden und wird von rund 10 Millionen Entwicklern auf der ganzen Welt verwendet. Neuere Versionen haben eine Reihe neuer Funktionen hinzugefügt, um jungen Programmierern das Erlernen dieser Sprache zu erleichtern und die Funktionen der Sprache und der API ohne Kompilierung und IDE zu nutzen. Beispielsweise führte Java SE 9 das JShell-Tool (REPL) ein, das die interaktive Programmierung unterstützt.

Mit der Veröffentlichung von JDK 11 wurde diese Sprache in die Lage versetzt, Skripte zu unterstützen, da Sie jetzt Code mit einem einfachen Aufruf des java Befehls ausführen können!

Es gibt zwei Möglichkeiten, Skripte in Java 11 zu verwenden:

  1. Direkter Aufruf des java Befehls.
  2. Verwenden von * nix-Skripten für die Befehlszeile, ähnlich wie bei Bash-Skripten.

Wir haben bereits über die erste Option nachgedacht, jetzt werden wir uns mit der zweiten befassen. Das eröffnet uns viele Möglichkeiten.

Shebang-Dateien: Führen Sie Java als Shell-Skript aus


Daher wurde in Java SE 11 die Unterstützung für Skripte angezeigt, einschließlich traditioneller Shebang-Dateien aus der * nix-Welt. Um sie zu unterstützen, war keine Sprachspezifikation erforderlich.

In der shebang-Datei müssen die ersten beiden Bytes 0x23 und 0x21 sein. Dies ist die ASCII-Zeichenkodierung #! .. Alle nachfolgenden Bytes in der Datei werden basierend auf dem Standardkodierungssystem auf dieser Plattform gelesen.

Damit die Datei mit dem im Betriebssystem integrierten shebang-Mechanismus ausgeführt werden kann, gibt es nur eine Voraussetzung: Die erste Zeile beginnt mit #! .. Dies bedeutet, dass wir keine spezielle erste Zeile benötigen, wenn der Java-Launcher explizit verwendet wird um den Code aus der Quelldatei auszuführen, wie dies bei HelloUniverse.java der Fall ist.

Führen Sie das folgende Beispiel in einem Terminal aus, auf dem macOS Mojave 10.14.5 ausgeführt wird . Aber zuerst definieren wir wichtige Regeln, die beim Erstellen einer shebang-Datei beachtet werden müssen:

  • Mischen Sie Java-Code nicht mit dem Skriptsprachencode Ihres OS-Shell-Skripts.
  • Wenn Sie Optionen für virtuelle Maschinen hinzufügen müssen, müssen Sie --source erste Option nach dem Namen der ausführbaren Datei in der shebang-Datei angeben. Zu den Optionen der virtuellen Maschine gehören: --class-path , --module-path , --add-exports , --add-modules , --limit-modules , --patch-module , --upgrade-module-path , sowie jegliche Variationen davon. In dieser Liste ist auch die neue Option --enable-preview , die in JEP 12 beschrieben wird .
  • Sie müssen die Java-Version angeben, die in der Quelldatei verwendet wird.
  • Die erste Zeile der Datei sollte mit Shebang-Zeichen (#!) Beginnen. Zum Beispiel:
    #!/path/to/java --source <vrsion>
  • Verwenden Sie für Java-Quelldateien NICHT den shebang-Mechanismus, um Dateien auszuführen, die der Standardbenennungskonvention entsprechen (endet in .java).
  • Sie müssen die Datei mit dem folgenden Befehl als ausführbar markieren:
    chmod +x <Filname>.<Extnsion> .

Erstellen wir eine shebang-Datei (Skriptprogramm), die den Inhalt des Verzeichnisses auflistet, dessen Name als Parameter übergeben wird. Wenn keine Parameter übergeben werden, wird standardmäßig das aktuelle Verzeichnis verwendet.

 #!/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); } } 

Speichern Sie den Code in einer Datei namens dirlist ohne die Erweiterung und markieren Sie ihn dann als ausführbar: mohamed_taman:code$ chmod +x dirlist .

Führen Sie die Datei aus:

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

Führen Sie es erneut mit dem Befehl aus, der das übergeordnete Verzeichnis übergibt, und überprüfen Sie das Ergebnis.

 mohamed_taman:code$ ./dirlist ../ 

Hinweis: Bei der Auswertung des Quellcodes ignoriert der Interpreter die Shebang-Zeile (erste Zeile). So kann die shebang-Datei mit dem Launcher explizit aufgerufen werden, zum Beispiel mit zusätzlichen Optionen:

 $ java -Dtrace=true --source 11 dirlist 

Es ist auch zu beachten: Wenn sich die Skriptdatei im aktuellen Verzeichnis befindet, können Sie sie folgendermaßen ausführen:

 $ ./dirlist 

Und wenn sich das Skript in einem Verzeichnis befindet, dessen Pfad im Benutzer PATH angegeben ist, können Sie es folgendermaßen ausführen:

 $ dirlist 

Abschließend gebe ich Ihnen einige Tipps, die Sie bei der Verwendung von Skripten berücksichtigen sollten.

Tipps


  1. Einige Optionen, die Sie an javac übergeben, werden möglicherweise nicht an java übergeben (oder nicht erkannt), z. B. die Optionen -Werror oder -Werror .
  2. Befinden sich im Klassenpfad .class- und .java-Dateien, erzwingt der Launcher die Verwendung der Klassendatei.

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

  3. Beachten Sie die Möglichkeit eines Konflikts zwischen Klassen- und Paketnamen. Schauen Sie sich diese Verzeichnisstruktur an:

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

    Beachten Sie die beiden java.java im HelloUniverse-Paket und die HelloUniverse.java-Datei im selben Verzeichnis. Wenn du versuchst zu rennen:

     mohamed_taman:code$ java HelloUniverse.java 

    dann wird welche datei zuerst ausgeführt und welche zweite? Der Launcher verweist nicht mehr auf die Klassendatei im HelloUniverse-Paket. Stattdessen wird die ursprüngliche HelloUniverse.java-Datei geladen und ausgeführt, d. H. Die Datei wird im aktuellen Verzeichnis gestartet.

Shebang-Dateien eröffnen eine Vielzahl von Möglichkeiten zur Erstellung von Skripten zur Automatisierung aller Arten von Aufgaben mit Java-Tools.

Zusammenfassung


Ab Java SE 11 und zum ersten Mal in der Programmiergeschichte können Sie Skripte mit Java-Code direkt ausführen, ohne sie zu kompilieren. Auf diese Weise können Sie Java-Skripte schreiben und über die * nix-Befehlszeile ausführen.

Experimentieren Sie mit dieser Funktion und teilen Sie Ihr Wissen mit anderen.

Nützliche Quellen


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


All Articles