飞码网-免费源码博客分享网站

点击这里给我发消息

Java理论与实践:无需编译即可运行单文件源代码程序|-Java教程

飞码网-免费源码博客分享网站 爱上飞码网—https://www.codefrees.com— 飞码网-matlab-python-C++ 爱上飞码网—https://www.codefrees.com— 飞码网-免费源码博客分享网站

为什么需要此功能

如果您想起Java SE 11(JDK 11)之前的年代,可以说您有一个HelloWorld.java源文件,其中包含类定义和静态main方法,该方法以单行文本的形式输出到终端:

public class HelloWorld {
      public static void main(String[] args) {
            System.out.println("Hello IBM Developer World");
      }
}

通常,要运行该类,首先需要使用Java编译器(javac对其进行编译,这将生成一个HelloWorld.class文件。

mohamed_taman$javac HelloWorld.java

然后,您将使用java解释器命令来运行生成的类文件:

mohamed_taman$ java HelloWorld
Hello IBM Developer World

这将启动JVM,加载类并执行代码。

本系列的更多

内容该内容是“ Java理论与实践”系列的一部分。

但是,如果您想快速测试一段代码,或者刚接触Java并想尝试该语言,该怎么办?在此过程中的这两个步骤似乎有些繁重。

在Java SE 11中,您可以选择直接启动单个源代码文件,而无需编译。

对于想尝试简单程序的语言新手来说,此功能特别有用。当将此功能与结合使用时jshell,您将获得一个不错的初学者的工具集。

专业人士还可以使用这些工具来探索新的语言更改或尝试未知的API。您还可以自动化任务,例如编写Java脚本,然后在操作系统级别将其作为脚本执行。

有关新Jshell 10+的更多信息,请参阅本系列中有关此主题的以下两个教程:

  • “ Java理论与实践:具有JShell 12,第1部分的交互式Java编程(REPL)”(IBM Developer,2019年4月)
  • “ Java理论与实践:具有JShell 12,第2部分的交互式Java编程(REPL)”(IBM Developer,2019年4月)

您需要遵循的内容

要运行本教程中提供的所有演示,您将需要下载Java SE 11 JDK。在本教程中,我使用纯文本编辑器而不是Java IDE,因为我想避免使用任何IDE魔术,而是直接在整个终端中使用Java命令行。

run .java和Java

JEP 330,启动单文件源代码程序,是JDK 11发行版中引入的令人兴奋的功能之一。此功能使您可以直接使用java解释器执行Java源代码文件源代码在内存中编译,然后由解释器执行。

功能的局限性

但是,此功能仅限于驻留在单个源文件中的代码。您不能添加其他源文件以在同一运行中进行编译。

要解决此限制,必须在同一文件中定义所有类,但是文件中的类数没有限制。

功能最简单的例子

现在,让我们以最简单的示例:Hello World!开始学习新事物时,我们始终会做的事情-完全是您所猜到的。

我不会深入研究该功能的实现细节,但将通过尝试不同的示例来集中精力演示如何使用此功能,以便您了解如何在编码中使用该功能。

如果尚未创建HelloWorld.java文件,则对其进行编译,然后运行生成的类文件。

现在,我希望您删除该类文件;一会儿您就会明白为什么:

mohamed_taman$rmHelloWorld.class

现在,如果仅使用java无编译运行类,如下所示:

mohamed_taman$ java HelloWorld.java
Hello IBM Developer World

您现在可以说javaHelloWorld.javaJava用完了,然后您看到我们只传入源代码文件而不是类文件,它在内部编译该源文件,然后运行生成的编译后的代码,最后将结果显示在控制台中。

因此,正在进行编译,如果出现编译错误,您将收到错误通知。另外,您可以检查目录结构,看看没有生成类文件。这是一个内存中的编译过程。

现在让我们看看这是如何发生的。

Java解释器如何能够运行HelloWorld

从JDK 10开始,Java启动器以三种模式运行:

  1. 运行一个类文件
  2. 运行JAR文件的主类
  3. 运行模块的主类

现在,我们添加新的第四种模式:运行在源文件中声明的类。

源文件模式由命令行上的两个项目确定:

  1. 命令行上的第一项既不是选项也不是选项的一部分
  2. --source <version>选项(如果存在)

对于第一项,Java命令会将命令行上的第一项(既不是选项也不是选项的一部分)视为要编译和运行的Java源文件。您仍然可以在源文件名之前为Java命令提供选项。例如,如果您希望在源文件使用外部依赖项时设置类路径。

对于第二项,如果filename标识具有.java扩展名的现有文件,则将选择源文件模式,然后将编译并运行该文件。--source选项可用于指定源代码的源版本。稍后我将详细讨论。

换句话说,Java将寻找第一个<FileName>.java请注意,我说的是文件名,而不是类名。

如果文件没有.java扩展名,则--source必须使用选项强制源文件模式。

对于源文件是要执行的脚本并且源文件的名称不遵循Java源文件的常规命名约定的情况,这是必需的(当我shebang在本教程的后面讨论文件时,将对此有更多信息。)

在源文件模式下,效果就像将源文件编译到内存中并执行在源文件中找到的第一个类。

您可以传递命令行参数吗?

让我们增强Hello World程序,为访问IBM Developer World的任何人创建个性化的问候:

public class HelloWorld{
    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 IBM Developer World!! %n", name);
    }
}

让我们将代码保存在名为的文件中Greater.java请注意,文件名与违反Java语言规范规则的公共类的名称不匹配。

运行以下代码,看看会发生什么:

mohamed_taman$ java Greater.java “Mo Taman”
Hello Mo Taman to IBM Developer World!!

如您所见,类名是否与文件名匹配无关紧要;它被编译在内存中,并且没有.class文件生成。

而且我知道您已经用鹰眼注意到了我是如何在要执行的文件名之后将参数传递给代码的。

因此,文件名之后出现在命令行上的所有参数都以main这种显而易见的方式传递给标准方法。

使用–source选项指定代码文件的级别

有两种情况可以使用该--source选项:

  1. 您可以使用source选项指定代码文件的源级别
  2. 您可以强制Java运行时进入源执行模式

在第一个选项中,当您省略源代码级别时,将假定它是当前的JDK版本,在本例中为11。启用第二个选项后,您现在还可以传递具有其他Java扩展名的文件,以进行编译和即时运行。

现在,让我们首先检查第二种情况,并重命名Greater.javagreater没有任何扩展名,然后尝试使用相同的方法再次执行它:

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

如您所见,在没有.java扩展名的情况下,Java命令解释器正在通过提供作为参数的名称来查找编译的类。在这种情况下,我们需要使用该--source选项来强制源文件模式:

mohamed_taman$ java --source 11 greater "Mo Taman"
Hello Mo Taman to IBM Developer World!!

现在让我们回到第一种情况。Greater.java班是JDK 10,因为它包含了兼容的var关键字,但不兼容JDK 9源切换到10,看看会发生什么:

mohamed_taman$ java --source 10 Greater.java “Mo. Taman”
Hello Mo. Taman to IBM Developer World!!

现在再次运行前面的命令,但将其传递给--source选项JDK 9而不是JDK 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

很简单,对不对?现在让我们看一下使用多个类。

您可以与多个班级一起工作吗?

答案是可以的。

检查一段包含两个类的示例代码,以表明该代码将要检查以确定特定给定的字符串是否是回文。回文是从两个方向读取相同内容的单词,词组,数字或其他字符序列,例如“女士”或“赛车”。

这是保存在文件名中的代码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 Palindrome!! %b %n",
                  args[0],
                  StringUtils
                        .isPalindrome(args[0]));
      }
}
public class StringUtils {
      public static booleanisPalindrome(String word) {
      return (new StringBuilder(word))
            .reverse()
            .toString()
            .equalsIgnoreCase(word);
      }
}

现在,让我们如下运行文件:

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

同样,将“ RaceCar”替换为“ MadAm”:

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

最后,让我们尝试用“ Mohamed”代替“ RaceCar”:

mohamed_taman:code$ java PalindromeChecker.java Mohamed
The string {Mohamed} is Palindrome!! False

如您所见,您可以在单个源文件中根据需要添加任意数量的公共类。唯一重要的是应该在源文件的第一类中定义main方法。编译器中的代码编译后,解释器(Java命令)将使用第一类来启动执行。

是否允许使用模块?

是的,完全允许使用模块。内存中已编译的代码作为带有选项–的未命名模块的一部分运行-add-modules=ALL-DEFAULT这使代码可以使用不同的模块,而无需使用显式声明依赖项module-info.java

让我们看一下使用JDK 11随附的新HTTP客户端API进行HTTP调用的代码。这些API是Java SE 9中的孵化器功能,但已逐步引入该java.net.http模块。

有关更多信息,请阅读``Java理论与实践:探索新的Java SE 11 HTTP客户端和WebSocket API''(IBM Developer,2019年4月)-有关此功能的完整教程。

在此示例中,我将调用一个简单的REST API GET方法来通过调用端点服务来获取一些用户https://reqres.in/api/users?page=2示例代码位于名称为的文件中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());
    }
}

如下运行:

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

这使您可以快速测试不同模块中的任何新功能,而无需创建模块。您还可以module-info为该模块定义文件,依此类推。

有关Java平台模块系统的更多信息,请参见JSR 376。

脚本,为什么需要它,为什么它很重要

首先,让我们了解什么是脚本编制,以了解为什么它是Java编程语言的重要组成部分。

我们可以将脚本定义如下:

“这是为特殊的运行时环境编写的程序,该程序可以自动执行任务,也可以由人一个接一个地执行。”

从这个通用定义中,我们可以得出脚本语言的简单定义:脚本语言是一种编程语言,它采用高级构造来一次解释和执行一个命令

  • 脚本使用文件中的一系列命令,这些命令无需编译即可执行-通常,脚本语言通常是解释而不是编译的
  • 与使用结构化和编译性强的语言(例如Java,C和C ++)相比,它们更易于学习,并且您可以使用它们更快地编写代码

服务器端脚本语言的很好的例子包括Perl,PHP和Python。在客户端是JavaScript。

长期以来,Java被归类为结构良好,强类型化和兼容的语言,仅由JVM解释为可在任何计算机体系结构上运行。这是对Java的主要抱怨。学习或使用其他脚本语言进行原型制作的速度并不快。

但是Java现在是一种成熟的语言,全世界大约有九十万个开发人员使用Java。为了使学习变得更容易,并且无需参与复杂的编译过程即可试用其功能和API,Java SE 9的发布增加了JShell REPL,它是一种支持交互式编程的工具。

JShell REPL使Java易于学习并可以快速进行实验。如果您不相信我,请跳到本系列中有关使用JShell的两个动手教程,并阅读它们。

那么,如何在Java 11中编写脚本?您可以通过java两种基本方式通过调用命令来运行代码

  • java直接使用命令工具
  • 使用* nix终端行脚本作为bash脚本

我们已经探讨了第一个选项,因此现在让我们探索第二个选项。

使用Java编写Shell脚本:Shebang文件

请记住,Java SE 11引入了对使用传统* nix shebang文件进行脚本编写的支持。支持此功能不需要更改Java语言规范。

在一般的Shebang文件中,前两个字节必须为“ 0x23”和“ 0x21”,即两个字符的ASCII编码“#!”。使用有效的默认平台字符编码读取所有后续字节。

第一行以#!仅当您要使用操作系统的shebang机制执行文件时才需要。HelloWorld.java我们之前示例一样,当显式使用Java启动器在源文件中运行代码时,您不需要特殊的第一行

创建shebang文件时,您还需要了解一些其他重要规则。

创建shebang文件的规则
不要Java代码与所需的操作系统外壳程序脚本语言混合使用。
如果需要包括虚拟机选项,则必须--source在shebang文件中的可执行文件名后指定第一个选项作为。这些选项包括:--class-path--module-path--add-exports--add-modules--limit-modules--patch-module--upgrade-module-path,和这些选项中的任何变异形式。它还可以包括--enable-previewJEP 12中所述的新选项。
必须在文件中指定用于源代码的Java语言版本。
shebang字符(#!)必须是文件的第一行,并且应如下所示:#!/path/to/java --source <version>
不允许使用shebang机制.java为Java源文件执行遵循标准命名约定的文件(以结尾的文件)
最后,您必须使用将文件标记为可执行文件chmod +x <Filename>.<Extension>

好戏开场了; 让我们创建一个shebang文件(脚本实用程序),该文件将列出传递目录的内容作为参数。如果目录未通过,则默认情况下将列出当前目录。该示例在使用MacOS Mojave 10.14.3的终端上运行。

源代码是:

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

将此代码保存在一个dirlist没有扩展名的文件中,然后将其标记为可执行文件:

mohamed_taman:code$chmod +x dirlist

如下运行:

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

通过传递父目录的以下命令再次运行它,然后自己检查输出。

mohamed_taman:code$ ./dirlist ../

注意,在评估源代码时,解释器会忽略shebang行(第一行)。shebang文件也可以由启动器显式调用,也许还有其他选项,例如:$ java -Dtrace=true --source 11 dirlist

另外,如果脚本文件在当前目录中,则可以这样执行它:

$ ./dirlist

或者,如果脚本位于用户PATH的目录中,则可以这样执行:

$ dirlist

最后,我想向您展示使用此功能时需要注意的一些技巧和陷阱。

提示,技巧和陷阱

使用此功能时,可能会遇到一些棘手的情况。

选项可能无法通过或无法识别

您可以传递给的某些选项javac可能不会被Java工具传递(或为此识别),例如-processor-Werror选项。

当您被迫使用类文件时

如果.class和都.java存在于类路径中,则必须使用类文件。

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

注意类和包的命名冲突

请记住类和包的命名冲突。检查以下目录结构:

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

现在注意这两个文件;java.javaHelloWorld软件包和HelloWorld.java当前目录中的文件看到问题了吗?这是一个讨厌的人。尝试运行时会发生什么:

mohamed_taman:code$ java HelloWorld.java

哪个文件将运行,第一个还是第二个?

java启动器不再引用HelloWorld包中的类文件相反,它将HelloWorld.java从源加载文件。结果是当前目录中的文件将运行。

我喜欢这个shebang功能,因为它为创建脚本提供了无限可能,这些脚本可以使用Java语言的强大功能自动执行很多工作。

您可以从GitHub获取本教程的源代码。

概括

从Java SE 11开始,并且是编程语言历史上的第一次,您可以直接执行包含Java代码的脚本,而无需进行编译。Java 11源代码执行功能使用Java编写脚本并直接从* nix命令行执行脚本成为可能。

立即开始尝试这项新功能。

飞码网-免费源码博客分享网站 爱上飞码网—https://www.codefrees.com— 飞码网-matlab-python-C++ 爱上飞码网—https://www.codefrees.com— 飞码网-免费源码博客分享网站
赞 ()
内容页底部广告位3
留言与评论(共有 0 条评论)
   
验证码: