网站首页 > java教程 正文
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的设计初衷是为了提供一个轻量级、交互式的编程环境,主要用于以下场景:
- 快速原型设计:快速验证代码片段和逻辑。
- 学习和教学:帮助初学者学习Java语言和API。
- 调试和测试:快速测试代码或调试小逻辑。
实际应用场景
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在实际项目开发中的应用和局限有了更深入的了解。
?
猜你喜欢
- 2024-09-11 Java 9最终发布可能会推迟8周,到9月21日
- 2024-09-11 JAVA9模块化详解(一)——模块化的定义
- 2024-09-11 java 9 更新内容整理(java基础知识点整理)
- 2024-09-11 轻松上手Java 9模块化开发核心原则与实践
- 2024-09-11 Java 9 揭秘-模块化系统(二)(java9安装教程)
- 2024-09-11 Java9系列第8篇-Module模块化编程
- 2024-09-11 Java 9揭秘-模块依赖(十二)(java9模块化为啥没大规模使用)
- 2024-09-11 Java 9揭秘-模块依赖(七)(java9是什么版本)
- 2024-09-11 Java9新特性Jigsaw和Jshell(java9 模块化 系统)
- 2024-09-11 Java9新特性中的模块化到底是什么
你 发表评论:
欢迎- 最近发表
-
- Java常量定义防暴指南:从"杀马特"到"高富帅"的华丽转身
- Java接口设计原则与实践:优雅编程的艺术
- java 包管理、访问修饰符、static/final关键字
- Java工程师的代码规范与最佳实践:优雅代码的艺术
- 编写一个java程序(编写一个Java程序计算并输出1到n的阶乘)
- Mycat的搭建以及配置与启动(mycat部署)
- Weblogic 安装 -“不是有效的 JDK Java 主目录”解决办法
- SpringBoot打包部署解析:jar包的生成和结构
- 《Servlet》第05节:创建第一个Servlet程序(HelloSevlet)
- 你认为最简单的单例模式,东西还挺多
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)