Java11开发秘籍-十二、使用 JShell 的读取求值打印循环(REPL)

作者: Java开发者

在本章中,我们将介绍以下配方:

  • 熟悉 REPL
  • 导航 JShell 及其命令
  • 评估代码片段
  • JShell 中的面向对象编程
  • 保存和恢复 JShell 命令历史记录
  • 使用 JShellJavaAPI

介绍

REPL代表读取求值打印循环,如名称所示,它读取在命令行上输入的命令,对其进行评估,打印评估结果,并对输入的任何命令继续此过程。

所有主要语言,如 Ruby、Scala、Python、JavaScript 和 Groovy,都有 REPL 工具。Java 缺少急需的 REPL。如果我们必须尝试一些示例代码,比如说使用SimpleDateFormat解析字符串,我们必须编写一个包含所有仪式的完整程序,包括创建一个类、添加一个主方法,然后是我们想要尝试的一行代码。然后,我们必须编译并运行代码。这些仪式使得实验和学习语言的特点变得更加困难。

使用 REPL,您只能键入您感兴趣的代码行,并且您将立即得到有关表达式语法是否正确以及是否给出所需结果的反馈。REPL 是一个非常强大的工具,特别是对于第一次使用这种语言的人来说。假设您想展示如何用 Java 打印Hello World;为此,您必须开始编写类定义,然后编写public static void main(String [] args)方法,最后,您将解释或尝试解释许多新手难以理解的概念。

无论如何,有了 Java9 和更高版本,Java 开发人员现在可以不再抱怨没有 REPL 工具了。一个名为 JShell 的新 REPL 正在与 JDK 安装捆绑在一起。因此,我们现在可以自豪地编写Hello World作为我们的第一个Hello World代码。

在本章中,我们将探索 JShell 的特性,并编写真正让我们感到惊讶和欣赏 REPL 功能的代码。我们还将看到如何使用 JShellJavaAPI 创建自己的 REPL。

熟悉 REPL

在本教程中,我们将介绍一些基本操作,以帮助我们熟悉 JShell 工具。

准备

确保安装了最新的 JDK 版本,其中包含 JShell。JShell 从 JDK 9 开始提供。

怎么做。。。

  1. 您应该将%JAVA_HOME%/bin(在 Windows 上)或$JAVA_HOME/bin(在 Linux 上)添加到您的PATH变量中。如果没有,请访问“在 Windows 上安装 JDK 18.9 并设置路径变量”和“在 Linux(Ubuntu,x64)上安装 JDK 18.9 ”,以及第一章“安装和一瞥 Java 11”的“配置路径变量”配方。
  2. 在命令行上,键入jshell并按Enter
  3. 您将看到一条消息和一个jshell>提示:

  1. 正斜杠/后跟 JShell 支持的命令,帮助您与 JShell 交互。就像我们尝试/help intro获得以下内容:

  1. 让我们打印一条Hello World消息:

  1. 让我们打印一条定制的Hello World消息:

  1. 您可以使用向上和向下箭头键浏览已执行的命令。

它是如何工作的。。。

jshell提示符处输入的代码片段被包装成刚好足够执行它们的代码。因此,变量、方法和类声明被包装在类中,表达式被包装在方法中,而方法又被包装在类中。其他东西,例如导入和类定义,保持原样是因为它们是顶级实体,也就是说,不需要在另一个类中包装类定义,因为类定义是可以自身存在的顶级实体。类似地,在 Java 中,import语句可以自己出现,它们出现在类声明之外,因此不需要包装在类中。

在接下来的菜谱中,我们将看到如何定义方法、导入其他包和定义类。

在前面的配方中,我们看到了$1 ==> "Hello World"。如果我们有一个没有任何相关变量的值,jshell会给它一个变量名,比如$1$2

导航 JShell 及其命令

为了利用工具,我们需要熟悉如何使用它,它提供的命令,以及我们可以用来提高效率的各种快捷键。在本教程中,我们将介绍在 JShell 中导航的不同方式,以及在使用 JShell 时提供的不同快捷键。

怎么做。。。

  1. 通过在命令行上键入jshell生成 JShell。您将收到一条欢迎信息,其中包含入门说明。
  2. 键入/help intro获取 JShell 的简要介绍:

  1. 键入/help以获取支持的命令列表:

  1. 要获取有关命令的更多信息,请键入/help <command>。例如,要获取关于/edit的信息,请键入/help /edit

  1. JShell 中支持自动完成。这使 Java 开发人员感到宾至如归。您可以使用TAB键调用自动完成:

  1. 您可以使用/!执行先前执行的命令,/line_number在行号处重新执行表达式。
  2. 要在命令行中导航光标,请使用Ctrl+A到达行的开头,使用Ctrl+E到达行的结尾。

评估代码片段

在此配方中,我们将查看执行以下代码段:

  • 导入语句
  • 类声明
  • 接口声明
  • 方法声明
  • 字段声明
  • 声明

怎么做。。。

  1. 打开命令行并启动 JShell。
  2. 默认情况下,JShell 导入一些库。我们可以通过发出/imports命令来检查:

  1. 让我们通过发出import java.text.SimpleDateFormat命令来导入java.text.SimpleDateForm。这将导入SimpleDateFormat类。
  2. 让我们宣布一个Employee类。我们将在每一行中发布一条语句,这样它就是一条不完整的语句,我们将以与任何普通编辑器相同的方式进行处理。下图将阐明这一点:
        class Employee{
          private String empId;
          public String getEmpId() {
            return empId;
          }
          public void setEmpId ( String empId ) {
            this.empId = empId;
          }
        }
    

您将获得以下输出:

  1. 让我们声明一个Employability接口,它定义了一个方法employable(),如下面的代码片段所示:
        interface Employability { 
          public boolean employable();
        }
    

前面的界面通过jshell创建时,如下图所示:

  1. 让我们声明一个newEmployee(String empId)方法,它用给定的empId构造一个Employee对象:
        public Employee newEmployee(String empId ) {
          Employee emp = new Employee();
          emp.setEmpId(empId);
          return emp;
        }
    
    JShell 中定义的上述方法如下所示:

  1. 我们将使用上一步中定义的方法创建一个声明Employee变量的语句:
        Employee e = newEmployee("1234");
    

下面的屏幕截图显示了前面的语句及其在 JShell 中执行时的输出。代码段e.get + Tab键生成 IDE 支持的自动完成:

还有更多。。。

我们可以调用一个未定义的方法。请看以下示例:

public void newMethod(){
  System.out.println("New  Method");
  undefinedMethod();
}

下图显示了newMethod()调用undefinedMethod()的定义:

但是,在定义所使用的方法之前,无法调用该方法:

public void undefinedMethod(){
  System.out.println("Now defined");
}

下图显示了正在定义的方法undefinedMethod(),然后可以成功调用newMethod()

只有在定义了undefinedMethod()之后才能调用newMethod()

JShell 中的面向对象编程

在这个方法中,我们将使用预定义的 Java 类定义文件,并将它们导入 JShell。然后,我们将在 JShell 中使用这些类。

怎么做。。。

  1. 我们将在本食谱中使用的类定义文件在本书的代码下载中的Chapter12/4_oo_programming中提供。
  2. 有三个类定义文件:Engine.javaDimensions.javaCar.java
  3. 导航到这三个类定义文件所在的目录。
  4. /open命令允许我们从文件中加载代码。
  5. 加载Engine类定义并创建Engine对象:

  1. 加载Dimensions类定义并创建Dimensions对象:

  1. 加载Car类定义并创建Car对象:

保存和恢复 JShell 命令历史记录

我们想在jshell中尝试一些代码片段,作为向新手解释 Java 编程的一种方法。此外,某种形式的代码片段执行记录对于学习该语言的人来说非常有用。

在这个配方中,我们将执行一些代码片段并将它们保存到一个文件中。然后,我们将从保存的文件中加载代码段。

怎么做。。。

  1. 让我们执行一系列代码片段,如下所示:
        "Hello World"
        String msg = "Hello, %s. Good Morning"
        System.out.println(String.format(msg, "Friend"))
        int someInt = 10
        boolean someBool = false
        if ( someBool ) {
          System.out.println("True block executed");
        }
        if ( someBool ) {
          System.out.println("True block executed");
          System.out.println("False block executed");
        }
        for ( int i = 0; i < 10; i++ ){
          System.out.println("I is : " + i );
        }
    

您将获得以下输出:

  1. 使用/save history命令将执行的代码片段保存到名为history的文件中。
  2. 使用/exit退出 Shell,并根据操作系统的不同,使用dirls列出目录中的文件。列表中将有一个history文件。
  3. 打开jshell并检查使用/list执行的代码段的历史记录。您将看到没有执行任何代码段。
  4. 使用/open history加载history文件,然后检查使用/list执行的代码段的历史记录。您将看到正在执行并添加到历史记录中的所有以前的代码段:

使用 JShellJavaAPI

JDK 11 提供了 Java API,用于创建用于评估 Java 代码片段的工具,如jshell。此 Java API 存在于jdk.jshell模块中。因此,如果您想在应用程序中使用 API,则需要声明对jdk.jshell模块的依赖关系。

在这个方法中,我们将使用 JShellJDKAPI 来评估简单的代码片段,您还将看到不同的 API 来获取 JShell 的状态。这个想法不是要重新创建 JShell,而是要展示如何使用它的 JDKAPI。

对于这个配方,我们不会使用 JShell;相反,我们将遵循使用javac编译和使用java运行的常规方法。

怎么做。。。

  1. 我们的模块将依赖于jdk.jshell模块。因此,模块定义如下所示:
        module jshell{
          requires jdk.jshell;
        }
    
  2. 使用create()方法或jdk.jshell.JShell.Builder中的生成器 API 创建jdk.jshell.JShell类的实例:
        JShell myShell = JShell.create();
    
  3. 使用java.util.Scanner阅读System.in中的代码片段:
        try(Scanner reader = new Scanner(System.in)){
          while(true){
            String snippet = reader.nextLine();
            if ( "EXIT".equals(snippet)){
              break;
            }
          //TODO: Code here for evaluating the snippet using JShell API
          }
        }
    
  4. 使用jdk.jshell.JShell##eval(String snippet)方法评估输入。评估将产生一个jdk.jshell.SnippetEvent列表,其中包含评估的状态和输出。前面代码段中的TODO将替换为以下行:
        List<SnippetEvent> events = myShell.eval(snippet);
        events.stream().forEach(se -> {
          System.out.print("Evaluation status: " + se.status());
          System.out.println(" Evaluation result: " + se.value());
        });
    
  5. 评估完成后,我们将打印使用jdk.jshell.JShell.snippets()方法处理的片段,该方法将返回Snippet处理的Stream
        System.out.println("Snippets processed: ");
        myShell.snippets().forEach(s -> {
          String msg = String.format("%s -> %s", s.kind(), s.source());
          System.out.println(msg);
        });
    
  6. 同样,我们可以打印活动方法和变量,如下所示:
        System.out.println("Methods: ");
        myShell.methods().forEach(m -> 
          System.out.println(m.name() + " " + m.signature()));
        System.out.println("Variables: ");
        myShell.variables().forEach(v -> 
          System.out.println(v.typeName() + " " + v.name()));
    
  7. 在应用程序退出之前,我们通过调用JShell实例的close()方法关闭JShell实例:
        myShell.close();
    

此配方的代码可在Chapter12/6_jshell_api中找到。您可以使用同一目录中可用的run.batrun.sh脚本来运行该示例。示例执行和输出如下所示:

它是如何工作的。。。

API 中的中心类是jdk.jshell.JShell类。此类是求值状态引擎,其状态随代码段的每次求值而修改。正如我们前面看到的,代码片段是使用eval(String snippet)方法进行评估的。我们甚至可以使用drop(Snippet snippet)方法删除先前评估的代码段。这两种方法都会导致jdk.jshell.JShell维持的内部状态发生变化。

传递给JShell评估引擎的代码片段分类如下:

  • 错误:语法输入错误
  • 表达式:可能产生或不产生某些输出的输入
  • 导入:导入语句
  • 方法:方法声明
  • 声明:声明
  • 类型声明:一种类型,即类/接口声明
  • 变量声明:变量声明

所有这些类别都包含在jdk.jshell.Snippet.Kind枚举中。

我们还看到了不同的 API 来执行经过评估的代码段、创建的方法、变量声明和其他特定的代码段类型。每个代码段类型都有一个扩展jdk.jshell.Snippet类的类作为支持。**

文章列表

更多推荐

更多
  • IOS开发者的AWS和DevOps指南-十、iOS 应用开发的持续交付渠道 Jenkins 管道公司,AWS 代码管道,摘要,Fastlane 测试阶段,AWS 设备场测试阶段,Fastlane 构建阶段,Fastlane 交付阶段,为 AWS 代码管道设置 Jenkins 环境,在 AWS 控制台上设置代码管
    Apache CN

  • IOS开发者的AWS和DevOps指南-九、将 AWS 设备群用于测试 AWS 设备群简介,为应用测试生成 ipa 包,设置设备场项目并安排测试运行,AWS 设备场 Jenkins 插件,使用 Jenkins 自动化 AWS 设备群测试,摘要,使用 AWS 控制台安排测试运行,使用 AWS CLI 计划测试
    Apache CN

  • IOS开发者的AWS和DevOps指南-八、使用 Fastlane 自动构建、测试和发布 使用 Fastlane 匹配和亚马逊 S3 设置代码签名,设置 Jenkins 环境,用 Fastlane 自动化测试和构建,自动发布到 App Store Connect,摘要,正在初始化 Fastlane 匹配,在亚马逊 S3 存储
    Apache CN

  • IOS开发者的AWS和DevOps指南-六、使用 AWS CodeCommit 的源代码管理 Git 基础,创建 AWS 代码提交存储库,在 AWS 代码提交中添加源代码,AWS 代码提交中分支,AWS 代码提交中的拉请求,摘要,Git 安装,初始化 Git 存储库,记录对 Git 存储库的更改,克隆和使用远程 Git 存储库,
    Apache CN

  • IOS开发者的AWS和DevOps指南-七、将 AWS CodeCommit 与 Jenkins 集成 Jenkins 代码提交插件,设置集成组件,配置插件,使用 AWS 代码提交源创建 Jenkins 作业,摘要,通过 AWS 控制台设置组件,通过 Terraform 设置组件,测试 AWS 代码提交插件, 当应用源代码存储在 AWS
    Apache CN

  • IOS开发者的AWS和DevOps指南-四、AWS 上的 macOS 服务器 Amazon EC2 Mac 服务器,部署 Amazon EC2 Mac 服务器,连接到 Amazon EC2 Mac 服务器,使用 Amazon CloudWatch 监控 EC2 Mac 服务器,清理 Amazon EC2 Mac
    Apache CN

  • IOS开发者的AWS和DevOps指南-五、在 macOS 实例上设置开发工具 增加 macOS 实例宗卷大小,设置 Xcode,陷害 Jenkins,建立 Fastlane,设置 GitLab,摘要,Xcode 命令行工具,供应 Jenkins 控制器,EC2 Mac 实例作为 Jenkins 构建代理,创建 G
    Apache CN

  • IOS开发者的AWS和DevOps指南-三、亚马逊网络服务(AWS)上的 DevOps 三、亚马逊网络服务AWS上的 DevOpsAWS 上的持续集成,AWS 上的连续交付,基础设施作为代码,监控和记录,摘要,AWS 代码提交,AWS 代码构建,AWS 程式码人工因素,AWS 代码部署,AWS 代码管道,AWS 云阵,AW
    Apache CN

  • IOS开发者的AWS和DevOps指南-二、从 Xcode 到 App Store Connect 标识符,应用商店连接,从 Xcode 上传构件,测试和发布,摘要,截图和应用详细信息,TestFlight 软件,应用提交, 在前一章中,我们看到了如何使用 Xcode 在物理 iPhone 设备和模拟器上构建和运行应用。我们将进一步探
    Apache CN

  • IOS开发者的AWS和DevOps指南-一、iOS 应用开发基础 开发要求,迅速发生的,摘要,苹果个人计算机,苹果开发者账户,Xcode 简介,创建应用,构建应用,Xcode 命令行工具, 为了开发 iOS 应用,苹果提供了几种工具和资源。iOS 应用可以用原生编程语言开发,如 Swift 或 Obj
    Apache CN

  • 近期文章

    更多
    文章目录

      推荐作者

      更多