JShell是什么?
Java Shell或JShell是官方提供的读取-求值-打印-循环,通常称为REPL,是在Java 9中引入的。它提供了一个交互式shell,用于快速原型、调试、学习Java及Java API,所有这些都不需要public static void main方法,也不需要在执行之前编译代码。此外,随着Java 10引入了var关键词,JShell简单了许多(而且更实用了)。
 
ASP站长网入门
注意:在这份指南中,为了使用关键词var,我们将使用Java 10,因此,为了跟着这份指南操作,你务必要确保至少已经安装了Java 10。
 
JShell的启动很容易,在命令行输入jshell即可。你会看到一条欢迎信息,而shell会等待你输入命令或任何合法的Java表达式。
 
$ jshell
|  Welcome to JShell -- Version 10.0.2
|  For an introduction type: /help intro
让我们执行第一条命令。在shell提示符下,输入var greeting = "hello",按下<enter>。你会看到下面的输出:
 
jshell> var greeting = "hello"
greeting ==> "hello"
你会注意到,它回显了greeting的值,确认当前值为hello。你可能还会注意到,你的表达式不需要分号。这是一个小而漂亮的特性!
 
为了完成我们的问候语,我们需要的一位听众。输入var audience =并按下<enter>。这次,JShell认识到,你的表达式不完整,并允许你在下一行继续输入。输入"world"并按下<enter>完成表达式。和前面一样,JShell会回显确认已设置的值。
 
jshell> var audience =
  ...> "world"
audience ==> "world"
Tab补全
你首先注意到的其中一件事情是,它完美集成了tab补全。
 
让我们把字符串greeting和audience串联起来,组成一个新变量saying。先输入var saying = gr,然后按下<tab>。你会看到,变量greeting自动补全了。用同样的方法输入变量audience,按下<enter>,就可以看到串联结果了。
 
jshell> var saying = gr<tab> + aud<tab>
saying ==> "helloworld"
Tab补全就和你预想的一样,自动补全唯一值或者在不确定时提供可能的值。它对之前输入的任何表达式、对象和方法均有效。注意,它对内置关键词无效。
 
如果你想要把变量saying变成大写,但是又没记住方法的具体名称,那么你只要输入saying.to,然后按下<tab>,就可以看到所有以to开头的所有有效方法了。
 
jshell> saying.to<tab>
toCharArray()   toLowerCase( toString()      toUpperCase(
可能有参数的方法显示时使用开括号,而没有参数的方法显示时使用闭括号。
 
错误
如果你不小心犯了个错误或者输入了一个非法表达式、方法或命令,那么JShell会立即反馈,显示错误,标注问题。
 
jshell> saying.subString(0,1)
|  Error:
|  cannot find symbol
|    symbol:   method subString(int,int)
|  saying.subString(0,1)
|  ^--------------^
方法签名
让我们调用toUpperCase方法,但推迟添加任何额外的参数,或者以一个圆括号结束。再次按下<tab>。这次,你会看到toUpperCase方法所有可用的方法签名;一个有一个Locale参数,另一个没有任何参数。
 
jshell> saying.toUpperCase(
Signatures:
String String.toUpperCase(Locale locale)
String String.toUpperCase()
 
<press tab again to see documentation>
文档(JavaDoc)
如果你第三次按下<tab>,你就会看到toUpperCase(Locale)方法的JavaDoc文档。
 
jshell> saying.toUpperCase(
String String.toUpperCase(Locale locale)
Converts all of the characters in this String to upper case ... (shortened for brevity)
继续按下<tab>,就可以依次查看所有可用的方法签名及其相关文档。
 
导入
让我们把这个例子扩展到其他的听众,如Universe和Galaxy,而不仅仅是hello world。首先创建一个名为audiences的列表,其中有三个不同的听众:world、universe、galaxy。使用List构造函数和Java 9提供的静态工厂方法,只需要一行代码即可实现。
 
jshell> var audiences = new ArrayList<>(List.of("world", "universe", "galaxy"))
audiences ==> [world, universe, galaxy]
注意,你不必使用完整限定类名(FQCN)来引用ArrayList,也不必import java.util包。这是因为,在默认情况下,JShell启动时会自动执行一些预定义导入,减少导入常用包或输入FQCN的麻烦。
 
下面是默认导入的包:
 
java.io.*
java.math.*
java.net.*
java.nio.file.*
java.util.*
java.util.concurrent.*
java.util.function.*
java.util.prefs.*
java.util.regex.*
java.util.stream.*
正如你所料,你也可以根据需要输入import <pkg_name>定义自己的导入,其中<pkg_name>是类路径上一个有效的软件包。
 
方法
现在,让我们定义一个方法getRandomAudience,用于随机选取一名听众。该方法接收一个听众列表(List<String>),随机返回列表中的一名听众。你可以直接在命令行中定义方法,就像你在类中定义方法一样,不过,你不需要定义一个类!
 
jshell> public String getRandomAudience(List<String> audiences) {
  ...> return audiences.get(new Random().nextInt(audiences.size()));
  ...> }
|  created method getRandomAudience(List<String>)
如果一切顺利,JShell会显示方法已经成功创建,可以使用了。
 
让我们尝试调用这个方法,并传递听众列表。多调用几次,确保每次获得不同的结果。
 
jshell> getRandomAudience(audiences)
$7 ==> "world"
 
jshell> getRandomAudience(audiences)
$8 ==> "universe"
 
jshell> getRandomAudience(audiences)
$9 ==> "galaxy"
这里有一件很有趣的事需要注意,在方法体中,你可以引用之前定义的任何变量和尚未定义的变量(稍后会详细介绍)。
 
让我们创建getRandomAudience方法的另外一个版本,它不接收参数,直接在方法体内使用我们的听众列表。
 
jshell> public String getRandomAudience() {
  ...> return audiences.get(new Random().nextInt(audiences.size()));
  ...> }
|  created method getRandomAudience()
再次执行几遍。
 
jshell> getRandomAudience()
$10 ==> "galaxy"
 
jshell> getRandomAudience()
$11 ==> "world"
 
jshell> getRandomAudience()
$12 ==> "galaxy"
我上面提到过,方法还可以使用尚未定义的变量。让我们定义一个名为getSeparator的新方法,返回一个可以用来分隔单词的值。不过,这一次,我们将使用一个未定义的变量wordSeparator。
 
jshell> public String getSeparator() {
  ...> return wordSeparator;
  ...> }
|  created method getSeparator(), however, it cannot be invoked until variable wordSeparator is declared
注意,JShell创建了getSeparator方法, 但告诉我们,在我们声明或定义wordSeparator变量之前,该方法不能使用。任何调用它的尝试都会产生一条类似的错误信息。
 
jshell> getSeparator()
|  attempted to call method getSeparator() which cannot be invoked until variable wordSeparator is declared
把变量wordSeparator简单地定义成一个空格,再次尝试调用它。
 
jshell> var wordSeparator = " "
wordSeparator ==> " "
 
jshell> getSeparator()
$13 ==> " "
有一点需要特别注意,你无法创建一个“顶级”静态方法。如果你这样做,就会收到一条警告信息,告诉你static关键词被忽略了。
 
jshell> public static String foobar(String arg) {
  ...> return arg;
  ...> }
|  Warning:
|  Modifier 'static'  not permitted in top-level declarations, ignored
|  public static void foobar(String arg) {
|  ^-----------^
临时变量
除了显式声明和定义的变量外,JShell会自动为任何未赋值表达式创建变量。在上一节调用getSeparator和getRandomAudience方法时,你可能已经注意到这些变量,我们称为“临时变量(Scratch Variables)”。
 
临时变量遵循一个固定的模式,以$开头,后面跟一个递增的数字。你可以像引用其他任何变量一样引用它们。例如,我们再次调用getRandomAudience方法,把结果作为System.out.println的参数。
 
jshell> getRandomAudience()
$14 ==> "galaxy"
 
jshell> System.out.println($14)
galaxy

在JShell中,你可以像创建方法一样创建类,一行一行输入,直到类结束。JShell会提醒你,类已创建。
 
jshell> public class Foo {
  ...> private String bar;
  ...> public String getBar() {
  ...> return this.bar;
  ...> }
  ...> }
|  created class Foo
在JShell中创建类(和方法)非常费力。没有格式,犯错会令人沮丧,因为在你完成这个类之前你都不知道自己已经犯错了。要了解更好的类创建方式,请查阅下一节里JShell命令的/open命令。
 
扩展类库
到目前为止,我们对JShell有了基本的了解,你可能会想知道,如何在JShell中使用外部类库(jars),如公司内部库或像Apache Commons这样的公共库。幸运的是,这很容易。你只要在启动JShell时使用--class-path参数。该参数使用带有分隔符的标准类路径格式。
 
$ jshell --class-path /path/to/foo.jar
JShell命令
到目前为止,我们仅仅使用了Java表达式,但JShell还提供了若干内置命令。让我们换个角度,探索下JShell中可用的命令。要查看所有可用命令的列表,在提示符下输入/help。注意,tab补全也适用于命令。
 
jshell> /help
|  Type a Java language expression, statement, or declaration.
|  Or type one of the following commands:
|  /list [<name or id>|-all|-start]
|       list the source you have typed
|  /edit <name or id>
|       edit a source entry
|  /drop <name or id>
|       delete a source entry
(shortened for brevity)
如果你想了解有关特定命令的详细信息,你可以输入/help <command>,用命令的名字代替<command>。
 
 
jshell> /help list
|
|                                   /list
|                                   =====
|
|  Show the snippets, prefaced with their snippet IDs.
让我们看一些最有用的命令。
 
List命令
/list命令输出之前输入的所有代码片段,而且每一段都有一个独一无二的标识,称为片段ID。
 
jshell> /list
  1 : var greeting = "hello";
  2 : var audience = "world";
  3 : var saying = greeting + audience;
  4 : saying.toUpperCase()
在默认情况下,输出不包含任何产生了错误的片段。只有有效的语句或表达式才会显示。
 
要查看之前输入的所有代码,包括错误,则可以给/list命令传入参数-all。
 
s1 : import java.io.*;
s2 : import java.math.*;
s3 : import java.net.*;
(shortened for brevity)
s10 : import java.util.stream.*;
1 : var greeting = "hello";
2 : var audience = "world";
3 : var saying = greeting + audience;
4 : saying.toUpperCase()
e1 : var thisIsAnError
输出会包含任何启动代码(稍后详细介绍)以及任何有效或无效的片段。JShell会根据片段的类型给每个片段ID添加一个前缀。下面是快速确定其意义的方法:
 
s:片段ID以s开头的是启动代码。
e:片段ID以e开头的产生了错误。
片段ID没有前缀的是有效片段。
Vars、Methods、Types、Imports和Reset命令
JShell提供了多个命令帮助你查看shell的当前状态或上下文。它们都有恰当的名称,而且简单易懂,但是完备起见,我们把它们都列在这里。
 
你可以使用/vars查看声明的所有变量和它们的值。
 
jshell> /vars
|    String greeting = "hello"
|    String audience = "world"
|    String saying = "helloworld"
你可以使用/methods命令列出声明的所有方法和它们的签名。
 
jshell> /methods
|    String getRandomAudience(List<String>)
|    String getRandomAudience()
你可以使用/types命令列出所有类型声明。
 
jshell> /types
|    class Foo
你可以使用/imports命令列出���前声明的所有导入。
 
jshell> /imports
|    import java.io.*
|    import java.math.*
|    import java.net.*
(shortened for brevity)
最后,你可以使用/reset命令重置和清理包括变量、方法和类型在内的所有状态。

dawei

【声明】:九江站长网内容转载自互联网,其相关言论仅代表作者个人观点绝非权威,不代表本站立场。如您发现内容存在版权问题,请提交相关链接至邮箱:bqsm@foxmail.com,我们将及时予以处理。