在JDK 11中启动单文件源代码程序

常用的方式

javac *.java会生成对应的*.class文件
java *.class就可以执行了,.class可以省略

单文件启动

JEP 330 –启动单文件源代码程序是即将发布的JDK 11(18.9)发行版中令人兴奋的功能之一。 此功能允许直接使用java解释器执行Java源代码。 源代码在内存中编译,然后由解释器执行。 限制是必须在同一文件中定义所有类。

该功能与jshell将是任何初学者学习该语言的绝佳工具集。 不仅它们,而且专业人员也可以使用这些工具来探索新的语言更改或尝试未知的API。

最简单的例子

下面的代码保存在文件HelloWorld.java

1
2
3
4
5
public class HelloWorld{
public static void main(String[] args){
System.out.println("Hello World!!!");
}
}

我将运行上面的代码,如下所示:

1
2
G:\samples\java11\single-file> java HelloWorld.java
Hello World!!!

在上面的示例中,只有一个类,并且包含main方法。 使用java执行代码时,我们需要将以.java扩展名结尾的文件的名称传递给它。 如果文件名不以.java扩展名结尾,那么我们必须使用--source选项,如我们在下一个示例中看到的那样。

带有命令行参数

让我们增强Hello Worl程序,为每个人创建个性化的问候:

1
2
3
4
5
6
7
8
9
public class Greeting{
public static void main(String[] args){
if ( args == null || args.length < 1 ){
System.err.println("Name required");
System.exit(1);
}
System.out.println(String.format("Hello %s!!", args[0]));
}
}

让我们将上面的代码存储在名为HelloGreeting.java的文件中。 请注意,文件名与公共类的名称不匹配。 让我们使用以下代码运行上面的代码:

1
2
PS G:\samples\java11\single-file> java HelloGreeting.Java sana
Hello sana!!

在要执行的文件名之后提供给代码的任何参数。 让我们将HelloGreeting.java重命名为greeting然后尝试使用相同的方法执行:

1
2
3
PS G:\samples\java11\single-file> java greeting sana
Error: Could not find or load main class greeting
Caused by: java.lang.ClassNotFoundException: greeting

您可以看到,在没有.java的情况下,解释器正在通过提供的名称作为参数来搜索编译的类。 在这种情况下,我们需要使用--source选项,如下所示:

1
2
PS G:\samples\java11\single-file> java --source 11 greeting sana
Hello sana!!

让我向您展示当使用--source选项时,为JDK 10编写的代码如何不适用于JDK 9:

1
2
3
4
5
6
public class Java10Compatible{
public static void main(String[] args){
var message = "Hello world";
System.out.println(message);
}
}

让我们对JDK 10和JDK 9执行以上操作,如下所示:

1
2
3
4
5
6
7
8
9
10
PS G:\samples\java11\single-file> java --source 10 Java10Compatible.java
Hello world
PS G:\samples\java11\single-file> java --source 9 Java10Compatible.java
.\Java10Compatible.java:3: error: cannot find symbol
var message = "Hello world";
^
symbol: class var
location: class Java10Compatible
1 error
error: compilation failed

单个文件中包含多个类

如前所述,此功能支持驻留在单个文件中的运行代码,因此文件中的类数没有限制。 让我们看一个包含两个类的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SimpleInterest{
public static void main(String[] args){
if ( args == null || args.length < 3 ){
System.err.println("Three arguments required: principal, rate, period");
System.exit(1);
}
int principal = Integer.parseInt(args[0]);
int rate = Integer.parseInt(args[1]);
int period = Integer.parseInt(args[2]);
double interest = Maths.simpleInterest(principal, rate, period);
System.out.print("Simple Interest is: " + interest);
}
}

public class Maths{

public static double simpleInterest(int principal, int rate, int period){
return ( principal * rate * period * 1.0 ) / 100;
}
}

让我们运行这个:

1
2
PS G:\samples\java11\single-file> java .\SimpleInterest.java 1000 2 10
Simple Interest is: 200.0

对于定义了多个类的文件,第一类应包含main方法,并且在内存中编译后的解释器将使用第一类来启动执行。

使用模块

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

让我们看一下使用新的HTTP客户端API进行HTTP调用的代码。 这些在Java 9中作为孵化器功能引入的API已从孵化器移至java.net.http模块。 示例代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.net.http.*;
import java.net.http.HttpResponse.BodyHandlers;
import java.net.*;
import java.io.IOException;

public class ExternalModuleDepSample{
public static void main(String[] args) throws Exception{
HttpClient client = HttpClient.newBuilder().build();
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(URI.create("https://reqres.in/api/users"))
.build();

HttpResponse<String> response =
client.send(request, BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
}
}

我们可以通过发出以下命令来运行以上代码:

1
2
3
4
5
6
7
8
9
G:\samples\java11\single-file>java ExternalModuleDepSample.java
200
{"page":1,"per_page":3,"total":12,"total_pages":4,
"data":[{"id":1,"first_name":"George","last_name":"Bluth",
"avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/calebogden/128.jpg"},
{"id":2,"first_name":"Janet","last_name":"Weaver",
"avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/josephstein/128.jpg"},
{"id":3,"first_name":"Emma","last_name":"Wong",
"avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/olegpogodaev/128.jpg"}]}

这使我们能够快速测试不同模块中的新功能,而无需创建模块, module-info文件等。

Shebang文件

Shebang文件是通过使用语法#!/path/to/executable作为#!/path/to/executable的第一行提供执行程序,可以直接在Unix系统上执行的那些文件。

让我们创建一个shebang文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/g/jdk-11/bin/java --source 11

public class SimpleInterest{
public static void main(String[] args){
if ( args == null || args.length < 3 ){
System.err.println("Three arguments required: principal, rate, period");
System.exit(1);
}
int principal = Integer.parseInt(args[0]);
int rate = Integer.parseInt(args[1]);
int period = Integer.parseInt(args[2]);
double interest = Maths.simpleInterest(principal, rate, period);
System.out.print("Simple Interest is: " + interest);
}
}

public class Maths{

public static double simpleInterest(int principal, int rate, int period){
if ( rate > 100 ){
System.err.println("Rate of interest should be <= 100. But given values is " + rate);
System.exit(1);
}
return ( principal * rate * period * 1.0 ) / 100;
}
}

如果文件名不遵循标准的Java文件名命名约定,则使用shebang中的source选项。 在我们的例子中,我们将上面的代码保存在一个名为simpleInterest的文件中,可以这样运行:

1
2
3
sanaulla@Sana-Laptop  /g/samples/java11/single-file (master)
$ ./simpleInterest 1000 20 2
Simple Interest is: 400.0

在Windows机器上,我使用了git安装随附的bash shell。 还有其他多种方式,例如Cygwin,Windows 10 Ubuntu支持等。

源代码可以在这里找到。


在JDK 11中启动单文件源代码程序
https://flepeng.github.io/021-Java-在JDK-11中启动单文件源代码程序/
作者
Lepeng
发布于
2021年5月14日
许可协议