Java9新特性:JShell

Java 9 引入了一项非常实用的新特性——JShell。JShell是一个交互式的编程工具,允许开发者在命令行中直接输入Java代码并查看其执行结果。这为Java提供了类似Python的REPL(Read-Eval-Print Loop)环境,极大地提高了代码验证和调试的效率。在本文中,我们将详细探讨JShell的基本功能、应用场景以及在实际项目开发中的实用性。

JShell的优势

1. 降低新手入门门槛

对于初学者来说,JShell无疑是一个福音。传统的Java编程需要创建类、方法等结构,这对新手来说可能有些复杂。而在JShell中,用户可以直接输入简单的Java语句并立即看到结果,这大大降低了学习曲线。

2. 提高小逻辑验证效率

JShell特别适合用于验证简单的小逻辑。相比之下,使用IDE可能需要创建完整的项目结构、编译和运行,这个过程相对繁琐。在JShell中,用户只需输入代码并按回车键即可看到结果。

3. 立即返回执行结果

JShell的一个显著优势是其即时性。用户只需输入代码,JShell立即返回执行结果。这种即时反馈机制对于快速测试和调试代码非常有用。

JShell代码与普通可编译代码的区别

1. 立即返回执行结果

在JShell中,只要输入语句完成,它就会立即返回执行结果,无需经过传统的编辑、编译和解释步骤。

示例:

java

jshell> int a = 10;
a ==> 10

jshell> int b = 20;
b ==> 20

jshell> a + b
$3 ==> 30

2. 支持变量的重复声明

在普通Java代码中,重复声明变量会导致编译错误。但在JShell中,允许重复声明变量,后面声明的会覆盖前面声明的。

示例:

java

jshell> int x = 5;
x ==> 5

jshell> int x = 10;  // 重新声明
x ==> 10

3. 支持独立的表达式

JShell允许输入独立的表达式,比如简单的加法运算,而不需要将其放在方法或类中。

示例:

jshell> 1 + 1
$1 ==> 2

4. 支持方法和类的定义

虽然JShell主要用于简单的代码验证,但它也支持方法和类的定义。

示例:

java

jshell> int add(int x, int y) {
   ...> return x + y;
   ...> }
|  创建方法 add(int, int)

jshell> add(3, 4)
$2 ==> 7

JShell多行代码输入的处理机制

1. 多行输入的需求

在实际开发中,代码往往并不是一行能够解决的,尤其是定义方法、类或复杂逻辑时。JShell需要能够处理多行代码输入,并在用户输入完成后正确地解析和执行这些代码。

2. 多行输入的实现

JShell通过智能的输入解析机制来处理多行代码。具体来说,JShell会检测用户输入的语句是否完整,如果未完整则继续等待用户输入,直到语句完整为止。

3. 示例

以下是一个简单的多行代码输入示例:

java

jshell> int add(int x, int y) {
   ...> return x + y;
   ...> }
|  创建方法 add(int, int)

jshell> add(5, 7)
$1 ==> 12

在这个示例中,用户输入了一个多行的方法定义,JShell正确地等待了所有行输入完成后才执行。

4. 内部实现

JShell的多行输入处理主要依赖于以下几个步骤:

读取输入

JShell通过标准输入读取用户输入的代码行。

java

BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
StringBuilder codeBuilder = new StringBuilder();
String line;

while ((line = reader.readLine()) != null) {
    codeBuilder.append(line).append("\n");
    // 检查输入的代码是否完整
    if (isCompleteCode(codeBuilder.toString())) {
        break;
    }
}
检查代码完整性

JShell需要一种机制来检查用户输入的代码是否完整。这个检查过程涉及到解析代码的语法结构,确保所有的代码块(如方法、类、条件语句等)都正确结束。

java

private boolean isCompleteCode(String code) {
    // 简单的检查示例,实际JShell使用更复杂的语法解析器
    return code.contains("}");
}
编译和执行代码

一旦确认用户输入的代码完整,JShell会使用Java编译器API编译代码,并通过反射机制执行代码。

java

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
String fullCode = "public class Temp { public static void main(String[] args) { " + codeBuilder.toString() + " } }";
int result = compiler.run(null, null, null, "-d", "out", fullCode);

if (result == 0) {
    Process process = Runtime.getRuntime().exec("java -cp out Temp");
    BufferedReader output = new BufferedReader(new InputStreamReader(process.getInputStream()));
    String outputLine;
    while ((outputLine = output.readLine()) != null) {
        System.out.println(outputLine);
    }
} else {
    System.out.println("Compilation error");
}

在JShell中加载和使用外部的Java类库

JShell并不仅仅局限于Java内置的API。我们也可以在JShell中加载和使用外部的Java类库,这使得JShell在实际开发和调试中更加灵活和实用。

使用外部Java类库的场景

在实际开发中,我们经常依赖于各种第三方库。例如,进行HTTP请求的Apache HttpClient库,进行JSON解析的Jackson库,或者进行数据库连接的JDBC驱动等。在JShell中,我们可以加载这些外部库,并在交互式环境中使用它们进行测试和调试。

加载外部Java类库的步骤

1. 下载外部库

首先,我们需要下载所需的外部Java库。通常情况下,这些库以JAR文件的形式提供。例如,我们可以从Maven中央仓库下载Jackson库。

2. 将JAR文件添加到类路径

在JShell启动后,我们需要将这些JAR文件添加到类路径中。JShell提供了/env命令,使我们能够动态修改运行时环境,包括类路径。

3. 使用外部库

一旦我们将外部库添加到类路径中,就可以在JShell中像使用标准Java类库一样使用它们。

示例:在JShell中使用Jackson库

以下是一个详细的示例,展示了如何在JShell中加载和使用Jackson库进行JSON解析。

1. 下载Jackson库

首先,从Maven中央仓库下载如下两个JAR文件:

  • jackson-databind-2.12.3.jar
  • jackson-core-2.12.3.jar
2. 启动JShell并添加JAR文件到类路径

启动JShell:

jshell

使用/env命令将下载的JAR文件添加到类路径:

java

jshell> /env -class-path /path/to/jackson-databind-2.12.3.jar:/path/to/jackson-core-2.12.3.jar

请将/path/to/替换为实际JAR文件所在的路径。

3. 使用Jackson库进行JSON解析

下面是一个简单的示例,展示了如何使用Jackson库将JSON字符串解析为Java对象。

首先,导入Jackson库的相关类:

java

jshell> import com.fasterxml.jackson.databind.ObjectMapper;
jshell> import com.fasterxml.jackson.core.JsonProcessingException;

定义一个示例Java类:

java

jshell> class Person {
   ...> public String name;
   ...> public int age;
   ...> }
|  创建类 Person

然后,使用ObjectMapper将JSON字符串解析为Person对象:

java

jshell> ObjectMapper mapper = new ObjectMapper();
mapper ==> com.fasterxml.jackson.databind.ObjectMapper@1d251891

jshell> String jsonString = "{\"name\":\"John Doe\",\"age\":30}";
jsonString ==> "{\"name\":\"John Doe\",\"age\":30}"

jshell> Person person = mapper.readValue(jsonString, Person.class);
person ==> Person@6d06d69c

jshell> System.out.println("Name: " + person.name + ", Age: " + person.age);
Name: John Doe, Age: 30

在JShell中处理多个版本的同一个外部库

处理多个版本的同一个外部库是一个常见且复杂的问题。不同的项目或模块可能依赖于同一个库的不同版本,这可能导致类路径冲突和版本不兼容问题。在JShell中,我们也需要处理类似的问题。

解决方案

1. 使用不同的JShell会话

最简单的解决方案是使用不同的JShell会话,每个会话只加载一个版本的库。这可以避免类路径冲突,但不适用于需要同时访问多个版本库的情况。

2. 手动管理类路径

在JShell中,用户可以手动管理类路径,确保在同一会话中只加载一个版本的库。通过/env命令,可以动态添加或移除类路径。

3. 使用模块化系统(Jigsaw)

Java 9引入了模块化系统(Jigsaw),可以用于隔离不同版本的库。虽然JShell本身是基于模块化系统的,但在实际使用中,直接在JShell中管理模块可能比较复杂。

4. 使用类加载器隔离

通过自定义类加载器,可以在同一JShell会话中加载不同版本的库。每个类加载器拥有独立的命名空间,可以避免类路径冲突。

示例:使用自定义类加载器

以下是一个详细的示例,展示了如何使用自定义类加载器在JShell中处理多个版本的同一个外部库。

1. 下载不同版本的库

假设我们需要使用两个版本的Jackson库:

  • jackson-databind-2.12.3.jar
  • jackson-databind-2.9.9.jar
2. 启动JShell并定义自定义类加载器

启动JShell:

jshell

定义自定义类加载器:

java

import java.net.URL;
import java.net.URLClassLoader;
import java.lang.reflect.Method;

class CustomClassLoader extends URLClassLoader {
    public CustomClassLoader(URL[] urls) {
        super(urls);
    }

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return super.loadClass(name);
    }
}
3. 加载不同版本的库

加载Jackson库的两个版本:

java

jshell> URL[] urlsV1 = { new URL("file:///path/to/jackson-databind-2.12.3.jar") };
urlsV1 ==> URL[1] { "file:///path/to/jackson-databind-2.12.3.jar" }

jshell> URL[] urlsV2 = { new URL("file:///path/to/jackson-databind-2.9.9.jar") };
urlsV2 ==> URL[1] { "file:///path/to/jackson-databind-2.9.9.jar" }

jshell> CustomClassLoader loaderV1 = new CustomClassLoader(urlsV1);
loaderV1 ==> CustomClassLoader@1d251891

jshell> CustomClassLoader loaderV2 = new CustomClassLoader(urlsV2);
loaderV2 ==> CustomClassLoader@1d251891
4. 使用不同版本的库

使用反射机制调用不同版本的库:

java

jshell> Class<?> objectMapperClassV1 = loaderV1.loadClass("com.fasterxml.jackson.databind.ObjectMapper");
objectMapperClassV1 ==> class com.fasterxml.jackson.databind.ObjectMapper

jshell> Object objectMapperV1 = objectMapperClassV1.getDeclaredConstructor().newInstance();
objectMapperV1 ==> com.fasterxml.jackson.databind.ObjectMapper@1d251891

jshell> Method readValueMethodV1 = objectMapperClassV1.getMethod("readValue", String.class, Class.class);
readValueMethodV1 ==> public java.lang.Object com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String,java.lang.Class) throws com.fasterxml.jackson.core.JsonProcessingException,java.io.IOException

jshell> Class<?> personClass = loaderV1.loadClass("Person");
personClass ==> class Person

jshell> Object personV1 = readValueMethodV1.invoke(objectMapperV1, "{\"name\":\"John Doe\",\"age\":30}", personClass);
personV1 ==> Person@6d06d69c

// Repeat similar steps for version 2
jshell> Class<?> objectMapperClassV2 = loaderV2.loadClass("com.fasterxml.jackson.databind.ObjectMapper");
objectMapperClassV2 ==> class com.fasterxml.jackson.databind.ObjectMapper

jshell> Object objectMapperV2 = objectMapperClassV2.getDeclaredConstructor().newInstance();
objectMapperV2 ==> com.fasterxml.jackson.databind.ObjectMapper@1d251891

jshell> Method readValueMethodV2 = objectMapperClassV2.getMethod("readValue", String.class, Class.class);
readValueMethodV2 ==> public java.lang.Object com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String,java.lang.Class) throws com.fasterxml.jackson.core.JsonProcessingException,java.io.IOException

jshell> Object personV2 = readValueMethodV2.invoke(objectMapperV2, "{\"name\":\"Jane Doe\",\"age\":25}", personClass);
personV2 ==> Person@6d06d69c

通过这种方式,我们可以在同一JShell会话中加载和使用不同版本的库,而不会发生类路径冲突。

在JShell中引入和管理多个类库

在实际开发和调试过程中,我们经常需要引入多个外部库来实现复杂的功能。JShell作为一个交互式编程工具,同样允许我们引入和管理多个类库。以下是详细的步骤和示例,展示如何在JShell中引入和使用多个类库。

引入多个类库的步骤

1. 下载所需的JAR文件

首先,从Maven中央仓库或其他可信来源下载所需的JAR文件。例如,我们需要使用Jackson库和Apache HttpClient库。

2. 启动JShell并设置类路径

启动JShell:

jshell

使用/env命令将下载的JAR文件添加到类路径中。假设JAR文件存放在/path/to/libs目录下:

java

jshell> /env -class-path /path/to/libs/jackson-databind-2.12.3.jar:/path/to/libs/jackson-core-2.12.3.jar:/path/to/libs/jackson-annotations-2.12.3.jar:/path/to/libs/httpclient-4.5.13.jar:/path/to/libs/httpcore-4.4.13.jar

确认类路径已经设置:

java

jshell> /env -class-path
|  类路径:
|    /path/to/libs/jackson-databind-2.12.3.jar
|    /path/to/libs/jackson-core-2.12.3.jar
|    /path/to/libs/jackson-annotations-2.12.3.jar
|    /path/to/libs/httpclient-4.5.13.jar
|    /path/to/libs/httpcore-4.4.13.jar
3. 使用外部库

接下来,我们将展示如何在JShell中使用Jackson库解析JSON数据,并使用Apache HttpClient库进行HTTP请求。

使用Jackson库解析JSON

首先,导入Jackson库的相关类:

java

jshell> import com.fasterxml.jackson.databind.ObjectMapper;
jshell> import com.fasterxml.jackson.core.JsonProcessingException;

定义一个示例Java类:

java

jshell> class Person {
   ...> public String name;
   ...> public int age;
   ...> }
|  创建类 Person

使用ObjectMapper将JSON字符串解析为Person对象:

java

jshell> ObjectMapper mapper = new ObjectMapper();
mapper ==> com.fasterxml.jackson.databind.ObjectMapper@1d251891

jshell> String jsonString = "{\"name\":\"John Doe\",\"age\":30}";
jsonString ==> "{\"name\":\"John Doe\",\"age\":30}"

jshell> Person person = mapper.readValue(jsonString, Person.class);
person ==> Person@6d06d69c

jshell> System.out.println("Name: " + person.name + ", Age: " + person.age);
Name: John Doe, Age: 30
使用Apache HttpClient库进行HTTP请求

导入Apache HttpClient库的相关类:

java

jshell> import org.apache.http.client.methods.CloseableHttpResponse;
jshell> import org.apache.http.client.methods.HttpGet;
jshell> import org.apache.http.impl.client.CloseableHttpClient;
jshell> import org.apache.http.impl.client.HttpClients;
jshell> import org.apache.http.util.EntityUtils;

使用HttpClient库进行HTTP请求:

java

jshell> CloseableHttpClient httpClient = HttpClients.createDefault();
httpClient ==> org.apache.http.impl.client.InternalHttpClient@1d251891

jshell> HttpGet request = new HttpGet("https://jsonplaceholder.typicode.com/posts/1");
request ==> GET https://jsonplaceholder.typicode.com/posts/1

jshell> CloseableHttpResponse response = httpClient.execute(request);
response ==> org.apache.http.impl.execchain.ResponseEntityProxy@1d251891

jshell> String responseBody = EntityUtils.toString(response.getEntity());
responseBody ==> "{
  \"userId\": 1,
  \"id\": 1,
  \"title\": \"sunt aut facere repellat provident occaecati excepturi optio reprehenderit\",
  \"body\": \"quia et suscipit
suscipit rerum et autem
nob...
}"

jshell> System.out.println(responseBody);
{
  "userId": 1,
  "id\": 1,
  "title\": \"sunt aut facere repellat provident occaecati excepturi optio reprehenderit\",
  "body\": \"quia et suscipit
suscipit rerum et autem
nob...
}

JShell在实际项目开发中的实用性

JShell的设计初衷

JShell的设计初衷是为了提供一个轻量级、交互式的编程环境,主要用于以下场景:

  1. 快速原型设计:快速验证代码片段和逻辑。
  2. 学习和教学:帮助初学者学习Java语言和API。
  3. 调试和测试:快速测试代码或调试小逻辑。

实际应用场景

1. 快速验证小逻辑

JShell非常适合用来验证小段代码或逻辑。例如,验证某个算法的正确性、测试某个API的返回结果等。对于这类场景,JShell非常高效。

示例:

java

jshell> int sum(int a, int b) {
   ...> return a + b;
   ...> }
|  创建方法 sum(int, int)

jshell> sum(5, 10)
$2 ==> 15
2. 学习和教学

JShell在学习和教学方面有着显著的优势。它提供了一个即时反馈的环境,帮助初学者快速了解Java语法和API。教师也可以使用JShell进行课堂演示,展示代码的执行过程。

示例:讲解循环语句

java

jshell> for (int i = 1; i <= 5; i++) {
   ...> System.out.println("Count: " + i);
   ...> }
Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
3. 调试和测试

在项目开发过程中,开发者经常需要调试和测试小段代码或验证某个API的返回结果。JShell提供了一个非常高效的环境,允许开发者快速进行这些操作。

示例:快速测试字符串操作

java

jshell> String text = "Hello, JShell!";
text ==> "Hello, JShell!"

jshell> text.toUpperCase()
$2 ==> "HELLO, JSHELL!"
4. 快速验证第三方库

在引入新的第三方库时,开发者可以使用JShell快速验证其功能,避免在完整项目中引入不必要的复杂性。

示例:验证HTTP请求库

java

jshell> /env -class-path /path/to/libs/httpclient-4.5.13.jar

jshell> import org.apache.http.client.methods.CloseableHttpResponse;
jshell> import org.apache.http.client.methods.HttpGet;
jshell> import org.apache.http.impl.client.CloseableHttpClient;
jshell> import org.apache.http.impl.client.HttpClients;
jshell> import org.apache.http.util.EntityUtils;

jshell> CloseableHttpClient httpClient = HttpClients.createDefault();
jshell> HttpGet request = new HttpGet("https://jsonplaceholder.typicode.com/posts/1");
jshell> CloseableHttpResponse response = httpClient.execute(request);
jshell> String responseBody = EntityUtils.toString(response.getEntity());
jshell> System.out.println(responseBody);

JShell的局限性

1. 不适用于复杂项目

JShell非常适合用于快速验证小段代码或逻辑,但对于复杂的项目和业务逻辑,它并不适合。复杂项目通常涉及多个模块、依赖关系和配置,这些在JShell中难以管理和维护。

2. 类路径和依赖管理复杂

在JShell中引入和管理多个类库可能会变得复杂,特别是当这些类库之间存在依赖关系时。虽然可以通过/env命令手动设置类路径,但这种方式并不适合复杂项目。

3. 无法完全替代IDE

尽管JShell提供了一个交互式编程环境,但它无法完全替代IDE。IDE提供了许多高级功能,如代码补全、调试、版本控制集成等,这些都是JShell无法提供的。

4. 性能和资源限制

JShell主要用于小段代码的测试和验证,对于需要大量计算和资源的任务,它并不是理想的选择。复杂的计算和资源密集型任务应当在完整的开发环境中进行。

高效利用JShell的建议

1. 结合IDE使用

将JShell作为IDE的辅助工具,在IDE中进行主要开发工作,而在JShell中进行快速验证和测试。

2. 使用脚本文件

JShell支持运行脚本文件(.jsh文件),这允许开发者将重复的测试和验证步骤自动化,提高效率。

示例:使用脚本文件

创建一个名为test.jsh的文件:

java

// test.jsh
int sum(int a, int b) {
    return a + b;
}

System.out.println("Sum: " + sum(5, 10));

在JShell中运行脚本文件:

jshell> /open test.jsh
Sum: 15

3. 动态加载和卸载类库

在JShell中,可以通过/env命令动态加载和卸载类库,以便快速验证不同版本的库或处理类路径冲突。

4. 调试和性能测试

使用JShell快速验证和调试代码片段,避免在完整项目中引入不必要的复杂性。对于性能测试,JShell可以用于验证小段代码的性能表现,但真正的性能测试应当在完整的开发环境中进行。

总结

JShell是一个非常有用的辅助工具,适用于快速验证、学习和调试小段代码。在实际项目开发中,合理利用JShell可以大大提高开发效率,但它并不能完全替代完整的开发环境。希望本文对你有所帮助,让你对JShell在实际项目开发中的应用和局限有了更深入的了解。

  • 21
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值