翻译:叩丁狼教育吴嘉俊
JEP330-启动单文件代码程序(Launch Single-File Source-Code Programs)是即将更新的JDK11(18.9)版本中一个很不错的功能。这个功能允许你直接使用java解析器运行java代码。java文件会在内存中执行编译并且直接执行。唯一的约束在于所有相关的类必须定义在东一个java文件中。
这个特征非常适合刚刚准备学习java的童鞋,或者想快速尝试一些简单代码。这个功能和jshell会成为所有java初学者的最强大的工具。不仅仅如此,所有的成熟的开发,可以使用这个工具来快速验证和学习新的API。
在本文中,我不会具体去探讨这个功能是如何实现的,相反,我们会集中精力在如何使用这个功能。好了,我们仍然从Hello World示例开始!
最基础的案例
把以下代码保存到Hello.java文件中:
public class HelloWorld{
public static void main(String[] args){
System.out.println("Hello World!!!");
}
}
我们将会按照下面的方法来执行上面的代码:
PS G:\samples\java11\single-file> java HelloWorld.java
Hello World!!!
在上面的例子,我们仅仅只是在一个类中包含了一个main方法。我们直接使用java命令去执行这个.java文件。如果这个文件不是以.java结尾,我们可以使用—source参数来执行,这个待会会看到。
包含命令行参数
接下来的案例,我们传入一个参数,允许给所有人打招呼:
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文件中。注意,这个文件名字和我们的类的名字不匹配。我们按照如下命令执行:
PS G:\samples\java11\single-file> java HelloGreeting.Java sana
Hello sana!!
任何一个跟在文件名后面的参数都被作为方法的参数传入方法执行。我们把HelloGreeting.java直接重新命名为greeting(注意,没有.java后缀),我们再次执行:
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结尾的情况下,java编译器会尝试直接使用传入的名称作为类名去寻找.class文件。在这种情况下,我们需要使用—source选项:
PS G:\samples\java11\single-file> java --source 11 greeting sana
Hello sana!!
有了—source选项,我们可以非常方便的演示在JDK10中写的代码不能再JDK9中执行的情况:
public class Java10Compatible{
public static void main(String[] args){
var message = "Hello world";
System.out.println(message);
}
}
我们分别在JDK10和JDK9下执行:
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
一个文件中包含多个类
文章开头我就提到,这个特性只是要求所有需要执行的代码是在同一个java文件中即可,而没有规定在这个java文件中只能有一个类。我们下面就来看看在一个java文件中包含多个类的情况:
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;
}
}
我们来运行这个代码:
PS G:\samples\java11\single-file> java .\SimpleInterest.java 1000 2 10
Simple Interest is: 200.0
在这个文件中,我们定义了多个类,但是在执行的时候,java编译器会运行这个文件中第一个类中的main方法(注:意思是,这个文件中的第一个类需要包含main方法,并且这个main方法作为运行的方法)
使用模块
在内存中编译和执行的类会作为一个添加了—add-modules=ALL-DEFAULT参数的匿名模块。这允许代码使用不同的模块,而不需要额外的定义module-info.java。
下面我们就来看一个使用了新HTTP Client API的HTTP请求案例。HTTP Client API是Java9作为独立的模块,放在java.net.http模块中的。下面的代码演示了这个特性:
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());
}
}
我们可以直接使用命令执行:
PS 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文件。Shebang文件是Unix系统中常见的文件,以#!/path/to/executable作为文件的开头第一行,可以作为脚本小程序直接运行一段代码。
我们来创建一个shebang文件:
#!/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文件来执行。比如我们上面的代码就可以保存在一个叫simpleInterest的文件中。我们需要按照下面的方式来运行:
sanaulla@Sana-Laptop /g/samples/java11/single-file (master)
$ ./simpleInterest 1000 20 2
Simple Interest is: 400.0
在windows下,我们只能使用bash shell来执行。当然,还有诸如Cygwin,Windows 10 Ubuntu Support等工具来执行。
注:JEP330中对Shebang的说明:
Single-file programs are also common when the task at hand needs a small utility program. In this context, it is desirable to be able to run a program directly from source using the “#!” mechanism on Unix-derived systems, such as macOS and Linux. This is a mechanism provided by the operating system which allows a single-file program (such as a script or source code) to be placed in any conveniently named executable file whose first line begins with #!
and which specifies the name of a program to “execute” the contents of the file. Such files are called “shebang files”.
原文:https://sanaulla.info/2018/07/05/launch-single-file-source-code-programs-in-jdk-11/