复制代码
public class HelloUniverse{ public static void main(String[] args) { System.out.println("Hello InfoQ Universe"); }}
正常情形下,如果要运行这个类,首先,须要利用 Java 编译器(javac)来编译它,编译后将天生一个 HelloUniverse.class 文件:
复制代码

mohamed_taman$ javac HelloUniverse.java
然后,须要利用一条 java 虚拟机(阐明器)命令来运行天生的字节码类文件:
复制代码
mohamed_taman$ java HelloUniverseHello InfoQ Universe
它将启动 JVM、加载类并实行代码。
但是,如果我们想快速测试一段代码,或者我们刚开始学习 Java(这里的关键词是 Java)并想实践这种措辞,该当怎么办呢?上述过程中的两个步骤实践起来彷佛还是有点难度。
在 Java SE 11 中,我们可以在无需任何中间编译的情形下,直接启动单个源代码文件。
这一特性对付那些想考试测验大略程序的 Java 新手来说特殊有用;当我们将这个特性与 jshell 结合起来利用时,我们将会得到一个很棒的初学者学习工具集。
更多关于 Jshell 10+ 的新信息,请查看视频教程“ Hands-on Java 10 Programming with JShell ”。
专业职员也可以利用这些工具来探索新的措辞变革或考试测验未知的 API。在我看来,当我们可以自动化地实行很多任务时,比如,将 Java 程序编写为脚本,然后在操作系统 shell 中实行这些脚本,它将会产生更强大的功能。这种组合不仅为我们供应了 shell 脚本的灵巧性,同时也供应了 Java 措辞的强大功能。我们将在本文的第二部分更详细地磋商这个问题。
该 Java 11 特性的伟大之处在于,它使我们可以无需任何编译即可直接运行 Java 单文件源代码。现在让我们深入地理解它的更多细节和其他有趣的干系主题。
我们须要遵照什么如果想要运行本文中供应的所有演示示例,我们须要利用 Java 的最新版本。它该当是 Java 11 或更高版本。当前的功能版本是Java SE 开拓工具包 12.0.1(终极版本可以从该链接得到,只需接管容许并单击与操作系统相匹配的链接即可)。如果想要理解更多的新特性,最新的 JDK 13 early access是最近更新的,可以从这个链接下载。
我们还该当把稳到,现在也可以从Oracle 和其他供应商(如 AdoptOpenJDK )处获取 OpenJDK 版本。
在本文中,我们利用纯文本编辑器而不是 Java IDE,由于我们想要避免任何 IDE 魔力,并在终端中直策应用 Java 命令行。
利用 Java 运行.java 文件JEP 330 启动单文件源代码程序(Launch Single-File Source-Code Programs),是 JDK11 发行版本中引入的新特性之一。该特性许可我们直策应用 Java 阐明器来实行 Java 源代码文件。源代码在内存中编译,然后由阐明器实行,而不须要在磁盘上天生.class 文件了。
但是,该特性仅限于保存在单个源文件中的代码。不能在同一个运行编译中添加其他源文件。
为了知足这个限定,所有的类都必须在同一个文件中定义,不过它对文件中类的数量没有限定,并且类既可声明为公共类,也可以不是,由于只要它们在同一个源文件中就没紧要。
源文件中声明的第一个类将被提取出来作为主类,我们该当将 main 方法放在第一个类中。以是类的顺序很主要。
第一个示例现在,让我们以学习新东西时的一向做法开始我们的学习吧,是的,你没有猜错,以一个最大略的“Hello Universe!” 示例开始。
我们将集中精力通过考试测验不同的示例来演示如何利用该特性,以便你理解如何在日常编码中利用该特性。
如果还没有准备好,请先创建本文顶部列出的 HelloUniverse.java 文件,编译它,并运行天生的字节码类文件。
现在,我希望你删除编译天生的类文件;你立时就会明白为什么:
复制代码
mohamed_taman$ rm HelloUniverse.class
现在,如果不编译,只利用 Java 阐明器运行该类,操作如下:
复制代码
mohamed_taman$ java HelloUniverse.javaHello InfoQ Universe
我们会看到它运行了,并返回和之前编译时相同的结果。
对付 java HelloUniverse.java 来说,我们传入的是源代码而不是字节码类文件,这就意味着,它在内部编译源代码,然后运行编译后的代码,末了将输出到掌握台。
以是,它仍旧须要进行一个编译过程,如果有编译缺点,我们仍旧会收到一个缺点关照。此外,我们还可以检讨目录构造,会创造并未天生字节码类文件;这是一个内存编译过程。
现在,让我们看看这个邪术是如何发生的。
Java 阐明器如何运行 HelloUniverse 程序在 JDK 10 中,Java 启动程序会以如下三种模式运行:
运行字节码类文件运行 JAR 文件中的 main 类运行模块中的 main 类现在,在 Java 11 中,又添加了一个新的第四模式:
运行源文件中声明的类在源文件模式下,运行效果就像是,将源文件编译到内存中,并实行可以在源文件中找到的第一个类。
是否进入源文件模式由命令行上的如下两项来决定:
在命令行中既不是选项也不是选项一部分的第一项。如果存在选项的话,它将是–source选项。对付第一种情形,Java 命令将查看命令行上的第一项,它既不是选项也不是选项的一部分。如果它有一个以.java 结尾的文件名,那么它将会被当作是一个要编译和运行的 Java 源文件。我们也可以在源文件名之前为 Java 命令供应选项。比如,如果我们希望在源文件中通过设置类路径来利用外部依赖项时。
对付第二种情形,选择源文件模式,并将第一个非选项命令行项视为要编译和运行的源文件。
如果文件没有.java 扩展名,则必须利用–source 选项来逼迫实行源文件模式。
当源文件是要实行的“脚本”,或者源文件的名称不遵照 Java 源文件的常规命名约定时,–source 选项是必要的。
–source 选项还可用于指定源代码的措辞版本。稍后我会详细谈论。
我们可以通报命令行参数吗?让我们丰富下“Hello Universe”程序,为访问 InfoQ Universe 的任何人创建一个个性化的问候:
复制代码
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); }}
我们将代码保存在一个名为 Greater.java 的文件中。请把稳,该文件的命名违反了 Java 编程规范,它的名称和公共类的名称不匹配。
运行如下代码,看看将会发生什么:
复制代码
mohamed_taman$ java Greater.java "Mo. Taman"Hello, Mo. Taman to InfoQ universe!!
我们可以看到的,类名是否与文件名匹配并不主要;它是在内存中编译的,并且没有天生 .class 文件。敏锐的读者可能还把稳到了,我们是如何在要实行的文件名之后将参数通报给代码的。这意味着在命令行上文件名之后涌现的任何参数都会以这种显式的办法通报给标准的 main 方法。
利用–source 选项指定代码文件的措辞版本有两种利用 --source 选项的场景:
指定代码文件的措辞版本逼迫 Java 运行时进入源文件实行模式在第一种情形下,当我们缺省代码措辞版本时,则假定它是当前的 JDK 版本。在第二种情形下,我们可以对除 .java 之外的扩展名文件进行编译并立即运行。
我们先研究一下第二个场景,将 Greater.java 重命名为没有任何扩展名的 greater,然后利用相同的方法,考试测验再次实行它:
复制代码
mohamed_taman$ java greater "Mo. Taman"Error: Could not find or load main class greaterCaused by: java.lang.ClassNotFoundException: greater
正如我们所看到的那样,在没有 .java 扩展名的情形下,Java 命令阐明器将以模式 1 的形式启动 Java 程序,它会根据参数中供应的文件名探求编译后的字节码类。为了防止这种情形的发生,我们须要利用 --source 选项来逼迫指定源文件模式:
复制代码
mohamed_taman$ java --source 11 greater "Mo. Taman"Hello, Mo. Taman to InfoQ universe!!
现在,让我们回到第一个场景。Greater.java 类与 JDK 10 兼容的,由于它包含 var 关键字,但与 JDK 9 不兼容。将源版本变动为 10,看看会发生什么:
复制代码
mohamed_taman$ java --source 10 Greater.java "Mo. Taman"Hello Mo. Taman to InfoQ universe!!
现在再次运行前面的命令,但通报到 --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 arrayvar name = args[0]; ^Greater.java:8: error: cannot find symbolvar name = args[0]; ^ symbol: class var location: class HelloWorld1 error1 warningerror: compilation failed
请把稳缺点的形式,编译器警告说,在 JDK 10 中 var 会成为一个受限定的类型名,但是由于当前是 Java 措辞 9 版本,以是编译仍会连续进行。但是,由于在源文件中找不到名为 var 的类型,以是编译失落败。
很大略,对吧?现在让我们看看如何利用多个类。
它是否适用于多个类?答案是肯定的。
让我们测试一段包含两个类的示例代码,以演示该特性可以适用于多个类。该代码的功能是考验给定的字符串是否为回文。回文可以是一个单词、短语、数字或其他字符序列,但它们从两个方向读取时,都能得到相同的字符序列,例如“redivider”或“reviver”。
如下是保存在名为 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); }}
现在,我们运行一下这个文件:
复制代码
mohamed_taman:code$ java PalindromeChecker.java RediVidErThe string {RediVidEr} is a Palindrome!! True
利用“RaceCar”代替“RediVidEr”后,再运行一次:
复制代码
mohamed_taman:code$ java PalindromeChecker.java RaceCarThe string {RaceCar} is a Palindrome!! True
末了,再利用“Taman”来代替“RaceCar”:
复制代码
mohamed_taman:code$ java PalindromeChecker.java TamanThe string {Taman} is a Palindrome!! false
正如我们看到的那样,我们可以在单个源文件中添加任意多个的公共类。唯一的要点是,main 方法该当在源文件的第一个类中定义。阐明器(Java 命令)将利用第一个类作为入口,在内存中编译代码并启动程序。
许可利用模块吗?是的,完备许可利用模块。内存中编译的代码作为不决名模块的一部分运行,该不决名模块带有 --add-modules=ALL-DEFAULT 选项,该选项许可访问 JDK 附带的所有模块。
这使得代码可以利用不同的模块,而无需利用 module-info.java 显式声明依赖项。
让我们来看一些利用 JDK11 附带的新的 HTTP 客户端 API 进行 HTTP 调用的代码。把稳,这些 API 是在 Java SE 9 中作为孵化器特性引入的,但是现在它们已经逐步发展成为 java.net.http 模块中的完全特性。
在本示例中,我们将通过 GET 方法调用一个大略的 REST API 来获取一些用户信息。我们将调用一个公共端点做事 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.javaResponse code is: 200The 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"}]}
这许可我们快速测试不同模块供应的新功能,而无需创建自己的模块。
更多关于新的 Java 平台模块系统(JPMS)的信息,请查看视频教程“ Getting Started with Clean Code Java SE 9 ”。
为什么脚本对 Java 来说很主要?首先,让我们回顾一下脚本是什么,以便于理解为什么在 Java 编程措辞中利用脚本如此主要。
我们可以给脚本作如下的定义:
“脚本是为特定的运行时环境编写的程序,它可以自动实行任务或命令,这些任务或命令也可以由操作职员逐个实行。”
在这个通用定义中,我们可以推导出脚本措辞的一个大略定义;脚本措辞是一种编程措辞,它利用高等布局器每次阐明并实行一个命令。
脚本措辞是一种编程措辞,它在文件中利用一系列命令。常日,脚本措辞是阐明措辞(而不是编译措辞),并且方向于过程式编程风格(只管一些脚本措辞也有面向工具的特性)。
一样平常来说,脚本措辞比更构造化的编译措辞(如 Java、C 和 C++)更随意马虎学习,也能更快地进行代码编写。做事真个脚本措辞有 Perl、PHP 和 Python 等,客户真个脚本措辞有 JavaScript。
长期以来,Java 被归类成一种构造良好的、强类型的编译措辞,经 JVM 阐明运行于任何打算机体系构造上。然而,对付 Java 的一个抱怨是,与普通脚本措辞比较,它的学习及原型开拓速率不足快。
然而,现在,Java 已经成为一门历经 24 年的措辞,全天下大约有 940 万的开拓职员在利用它。为了让年轻一代的程序员更随意马虎地学习 Java,并在不须要编译和 IDE 的情形下考试测验其特性和 API,Java 最近发布了一些特性。从 Java SE 9 开始,添加了一个支持交互式编程的JShell (REPL) 工具集,其目的便是使 Java 更易于编程和学习。
现在,利用 JDK 11,Java 逐步成为一种支持脚本的编程措辞,由于我们可以大略地通过调用 Java 命令来运行代码了!
在 Java 11 中,有两种基本的脚本编写方法:
直策应用 java 命令工具。利用 nix 命令行脚本,它类似于 bash 脚本我们已做生意量过了第一种方法了,以是现在是时候看一下第二种方法,这是一个可以打开许多可能性大门的特性。
Shebang 文件:以 shell 脚本的形式运行 Java如前所述,Java SE 11 引入了对脚本的支持,包括支持传统的 nix,即所谓的 Shebang 文件。无需修正JLS(Java Language Specification,Java 措辞规范)就可以支持该特性。
在一样平常的 Shebang 文件中,前两个字节必须是 0x23 和 0x21 ,这是"#!"两个字符的 ASCII 编码。然后,才能有效地利用默认平台字符编码读取文件所有后续字节。
因此,当希望利用操作系统的 Shebang 机制实行文件时,文件的第一行须要以#! 开始。这意味着,当显式利用 Java 启动程序运行源文件代码时,无需任何分外的第一行,比如上面的 HelloUniverse.java 示例。
让我们在 macOS Mojave 10.14.5 的终端中运行下一个示例。但是首先,我们须要列出一些创建 Shebang 文件时,该当遵照的主要规则:
不要稠浊利用 Java 代码与操作系统的 shell 脚本措辞。如果须要包含VM(虚拟机)选项,则必须将 --source 指定为 Shebang 文件可实行的文件名后面的第一个选项。这些选项包括:–class-path、–module-path、–add-exports、–add-modules、–limit-modules、–patch-module、upgrade-module-path ,以及这些选项的任何变体形式。它还可以包括 JEP 12 引入的新的–enable-preview 选项。必须为文件中的源代码指定 Java 措辞版本。Shebang 字符(#!)必须在文件的第一行,它该当是这样的:复制代码
#!/path/to/java --source <version>
不许可利用Shebang 机制来实行遵照标准命名约定(以 .java 结尾的文件)的 Java 源文件。末了,必须利用以下命令将文件标记为可实行文件:
复制代码
chmod +x <Filename>.<Extension>.
在我们的示例中,我们创建一个 Shebang 文件(script utility program),它将列出作为参数通报的目录内容。如果没有通报任何参数,则默认列出当前目录。
复制代码
#!/usr/bin/java --source 11import 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$ ./dirlistWill 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
或者,如果脚本在用户路径的目录中,也可以这样实行:
复制代码
$ dirlist
末了,我们通过展示一些利用该特性时须要把稳的用法和技巧来结束本文。
用法和技巧可以通报给 javac 的一些选项可能不会被 Java 工具所通报 (或识别),比如, -processor 和 -Werror 选项。如果类路径中同时存在.class 和.java 文件,启动程序将逼迫利用字节码类文件。复制代码
mohamed_taman:code$ javac HelloUniverse.javamohamed_taman:code$ java HelloUniverse.javaerror: class found on application class path: HelloUniverse
请记住类和包存在命名冲突的可能性。请看如下的目录构造:
复制代码
mohamed_taman:code$ tree.├── Greater.java├── HelloUniverse│ ├── java.class│ └── java.java├── HelloUniverse.java├── PalindromeChecker.java├── UsersHttpClient.java├── dirlist└── greater
把稳:HelloUniverse 包下的两个 java.java 文件和当前目录中的 HelloUniverse.java 文件。当我们试图运行如下命令时,会发生什么呢?
复制代码
mohamed_taman:code$ java HelloUniverse.java
运行哪个文件,第一个还是第二个?Java 启动程序不再引用 HelloUniverse 包中的类文件。相反,它将通过源代码模式加载并运行 HelloUniverse.java 文件,以便运行当前目录中的文件。
我喜好利用 Shebang 特性,由于它为利用 Java 措辞的强大功能来创造脚本自动化完成大量事情供应了可能性。
总结从 Java SE 11 开始,在这款编程措辞的历史上,首次可以在无需编译的情形下,直接运行包含 Java 代码的脚本。Java 11 源文件实行特性使得利用 Java 编写脚本并直策应用 inx 命令行实行脚本成为可能。
转发+关注 私信我回答头条666 领取资料