专业的JAVA编程教程与资源

网站首页 > java教程 正文

Java9新特性:JShell(java9大功能)

temp10 2024-09-11 09:15:25 java教程 8 ℃ 0 评论

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

Java9新特性:JShell(java9大功能)


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在实际项目开发中的应用和局限有了更深入的了解。

?

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表