jshell使ってみた

jshellをつかってみました。
サイトに載っているサンプル実行するなどでは十分使えました。わざわざIDEでプロジェクト作ってー、main作ってーってやらなくていいので楽でした。
ただクラスを何個か新規で定義するほどの規模になったら、ちゃんと開発環境構築して、デバックできるほうがいいのかな、と思いました。


[Java Shellユーザー・ガイド](https://docs.oracle.com/javase/jp/11/jshell/index.html#Java-Platform-Standard-Edition) より
> ■ JShellの概要
Java Shellツール(JShell)は、Javaプログラミング言語を学習したり、およびJavaコードのプロトタイプを作成するための対話型ツールです。JShellはRead-Evaluate-Print Loop (REPL)であり、入力時に宣言、文および式を評価し、ただちに結果を表示します。ツールは、コマンドラインから実行されます。

> ■JShellを使用する理由
JShellを使用すると、一度に1つずつプログラム要素を入力したり、ただちに結果を確認したり、必要に応じて調整できます。
Javaプログラムの開発には、通常、次のプロセスが含まれます。
・完全なプログラムを作成します。
・これをコンパイルして、エラーを修正します。
・プログラムを実行します。
・問題がある場所を調べます。
・これを編集します。
のプロセスを繰り返します。
JShellを使用すると、プログラム開発時にコードを試したり、オプションを容易に調べることができます。JShellセッションで、個々の文をテストしたり、様々なバリエーションのメソッドを試したり、よく知らないAPIを試すことができます。JShellはIDEに取ってかわるものではありません。プログラムの開発時に、コードをJShellに貼り付けてこれを試してから、動作するコードをJShellからプログラム・エディタまたはIDEに貼り付けます。



# 環境構築

## java JDKのインストール
さすがにJDKはインストールしないとだめです。

### ダウンロード
[ここ](https://www.oracle.com/java/technologies/downloads/#jdk20-windows)から、とりえず現時点で最新の20をダウンロードして、インストール。

# 使ってみよう

## 起動・停止

### 起動
コマンドプロンプトかpownershellで「jshell」と入力。

```console
jshell
```

起動するとこんな感じ。
```console
C:\xxxxx>jshell
|  JShellへようこそ -- バージョン18
|  概要については、次を入力してください: /help intro

jshell>
```

### 停止

「/exit」と入力。

```console
jshell> /exit
|  終了します
```

## 四則演算

```console
jshell> 10 + 10
$1 ==> 20
```

```console
jshell> (10 + 10) * 100
$2 ==> 2000
```

### 数値型について

```console
jshell> 22 / 7
$4 ==> 3
```

基本、整数っぽい。
浮動小数点型にしたい時は、どちらかを浮動小数点型にする。

```console
jshell> 22 / 7.0
$5 ==> 3.142857142857143
```

## 文字列演算

```console
jshell> "ABC" + "DEF"
$3 ==> "ABCDEF"
```    

## 比較演算

```console
 jshell> 1 == 1
$6 ==> true
```

```console
jshell> 1 == 2 || 3 == 3
$1 ==> true
```

```console
jshell> 1 == 2 || 3 == 3 && (4 == 5)
$2 ==> false
```

※文字列はString クラスの equals メソッドを使用ですよ。

## メソッド

### メソッド一覧

メソッド名のスペルわすれた！の時のために。

あとえば、文字列のメソッド一覧を表示したい時は
「"文字列".」と入力して、[TAB]キー。
※ドットの入力わすれないように。

```console
jshell> "test".
charAt(                chars()                codePointAt(           codePointBefore(       codePointCount(
codePoints()           compareTo(             compareToIgnoreCase(   concat(                contains(
contentEquals(         describeConstable()    endsWith(              equals(                equalsIgnoreCase(
formatted(             getBytes(              getChars(              getClass()             hashCode()
indent(                indexOf(               intern()               isBlank()              isEmpty()
lastIndexOf(           length()               lines()                matches(               notify()
notifyAll()            offsetByCodePoints(    regionMatches(         repeat(                replace(
replaceAll(            replaceFirst(          resolveConstantDesc(   split(                 startsWith(
strip()                stripIndent()          stripLeading()         stripTrailing()        subSequence(
substring(             toCharArray()          toLowerCase(           toString()             toUpperCase(
transform(             translateEscapes()     trim()                 wait(
jshell> "test".
```

### メソッド

```console
jshell> "test".toUpperCase()
$3 ==> "TEST"
```

```console
jshell> Math.round(4.5)
$1 ==> 5
```

チェーンもOK。

```console
jshell> "test".toUpperCase().charAt(2)
$2 ==> 'S'
```

## 変数も使えるぞ！

```
var [変数名] = [値]
```

```console
jshell> var str1 = "ABC"
str1 ==> "ABC"

jshell> var str2 = "DEF"
str2 ==> "DEF"

jshell> str1 + str2
$5 ==> "ABCDEF"
```

型変換は不可能。

```console
jshell> /var str1
|    String str1 = "abc"

jshell> str1 = 1
|  エラー:
|  不適合な型: intをjava.lang.Stringに変換できません:
|  str1 = 1
|         ^
```


### 型指定して変数宣言

```console
jshell> String str3 = "XYZ"
str3 ==> "XYZ"

jshell> /var str3
|    String str3 = "XYZ"
```


配列も宣言できる。

```console
jshell> int[] array = {1, 2, 3}
array ==> int[3] { 1, 2, 3 }

jshell> array[0]
$13 ==> 1
```

宣言と代入のステートメントをわけることもOK。

```console
jshell> String str4
str4 ==> null

jshell> str4 = "QWE"
str4 ==> "QWE"
```

## メソッドも宣言できるぞ！

```java
int addtion(int a, int b) {
  return a + b;
}
```

```console
jshell> int addtion(int a, int b) {
   ...>     return a + b;
   ...> }
|  次を作成しました: メソッド addtion(int,int)

jshell> addtion(1,3)
$21 ==> 4
```

エラーが有る時は、メソッドブロック最後の「}」入力した時に、エラー表示される。

```console
jshell> int addtion(int a, int b) {
   ...>     return a + b
   ...> }
|  エラー:
|  ';'がありません
|      return a + b
|                  ^
```

エラーになった場合は、「上矢印キー」を入力すると、メソッドの編集モードになるので、
エラー箇所を修正する。

メソッドはまずサクラエディタなのでコーディングして、それをコピペしたほうが楽です。
※インデントでタブ文字は禁止です。タブがあるとタブコマンドと認識されてエラーになる。インデントは半角スペースで。もちろんインデント無しでもOK。

サクラエディタで
```java
int addtion(int a, int b) {
 return a + b;
}
```
とコーディングして、jshellにコピペ、エンター。

```console
jshell> int addtion(int a, int b) {
   ...>      return a + b;
   ...> }
|  次を変更しました: メソッド addtion(int,int)
```

### ステートメントをエディタで編集
既存のメソッドを編集する時は、上記のように外部エディタからコピペでもOK。
もしくは、編集したいステートメント番号を調べて「/edit [ID]」でもデフォルトエディタが起動される。

```console
jshell> /list

   1 : 1 + 2
   2 : int addtion(int a, int b) {
            return a + b;
       }
```

```console
/edit 2
```

エディタが起動される。


## 前方参照
    
JShellでは、未定義のメソッド、変数またはクラスを参照するメソッド定義が使用可能。
ワーニングにはなるが、未宣言の「RATE_SALES_TAX」を使うことが出来る。

```console
// RATE_SALES_TAX未宣言状態で参照メソッド作成
jshell> int getSalesTax(int price) {
   ...>       return (int)Math.floor(price * RATE_SALES_TAX);
   ...> }
|  次を作成しました: メソッド getSalesTax(int)。しかし、 variable RATE_SALES_TAXが宣言されるまで、起動できません

jshell> final double RATE_SALES_TAX = 0.1
RATE_SALES_TAX ==> 0.1

jshell> getSalesTax(100)
$8 ==> 10
```

## クラスも定義できるぞ！

```java
public class Person {
  private String name;
  public Person(final String name) {
    this.name = name;
  }
  public String getName() {
    return name;
  }
}
```

```console
jshell> public class Person {
   ...>       private String name;
   ...>       public Person(final String name) {
   ...>             this.name = name;
   ...>       }
   ...>       public String getName() {
   ...>             return name;
   ...>       }
   ...> }
|  次を作成しました: クラス Person

jshell> Person aoki = new Person("AOKI");
aoki ==> Person@736e9adb

jshell> aoki.getName()
$11 ==> "AOKI"
```

## 外部モジュールもつかえるぞ！

必要なこと。

- jshell でクラスパス指定
- 使前にクラスをimport


ためしに、commons-lang3-3.12.0.jarのStringUtilを使ってみた。

### クラスパス指定してjshell起動

外部モジュールのjarを指定。

```console
jshell --class-path ./lib/commons-lang3-3.12.0.jar
```

起動した後であれば、「/env」コマンドでもOK。

```console
jshell> /env --class-path ./lib/commons-lang3-3.12.0.jar
|  新しいオプションの設定と状態の復元。
```


### インポート

```console
jshell> import org.apache.commons.lang3.StringUtils;
```

### 使ってみる

```console
jshell> StringUtils.isEmpty(null);
$2 ==> true
```

使えた。

## 複数行のステートメント
1ステートメントが長すぎて1行だと見づらい場合は、「(」を開いたままで改行するか、行の末尾を「.」で終わると複数入力可能です。

### 「(」を開いたままで改行
```console
doSSometiong(A, B, C
D, E, F);
```

### 行の末尾を「.」で終わる
```console
int sum = array.stream().
filter(element -> element.getParam1().equals("A")).
mapToInt(element -> element.getParam2()).
sum();
```

「.」先頭は別ステートメント扱いになりエラーになる。
サイトでのサンプルでメソッドチェーンを複数行で記載している場合、「.
」を先頭に記述しているのがほとんど。なのでコピペの際は気をつけること。
```console
// エラーになる。
int sum = array.stream().
filter(element -> element.getParam1().equals("A"))
.mapToInt(element -> element.getParam2())
.sum();
```

コピペしたらエラーになった。

```console
jshell> int sum = array.stream().
   ...> filter(element -> element.getParam1().equals("A"))
|  エラー:
|  不適合な型: java.util.stream.Stream<MyClass>をintに変換できません:
|  int sum = array.stream().
|            ^--------------...

jshell> .mapToInt(element -> element.getParam2())
|  エラー:
|  式の開始が不正です
|  .mapToInt(element -> element.getParam2())
|  ^

jshell> .sum();
```

### 編集モードを抜ける
編集モードの時、プロンプトは「...>」になっている。コピペ失敗なので編集モードを抜けたい時は、「Ctl+C」で抜ける。抜けた場合、作業中の変更は破棄されるので注意。

```console
jshell> inat addtion(int a, int b) {
   ...> 
```

「Ctl+C」
```console
jshell> inat addtion(int a, int b) {
   ...> 
jshell>    <= 抜けた   
```

# jshellコマンド

## 宣言済みの変数の型と値を確認

### 特定のもの

```
/var [変数名]
```
```console
jshell> /var str1
|    String str1 = "abc"
```

### 宣言済みの変数全て

```console
/vars
```

```console
jshell> /vars
|    long $1 = 5
|    char $2 = 'S'
|    String str2 = "DEF"
|    String $5 = "ABCDEF"
|    String str1 = "abc"
|    String $9 = "SSSSDEF"
|    String $11 = "abcDEF"
|    int[] array = int[3] { 1, 2, 3 }
|    int $13 = 1
|    String str3 = "XYZ"
|    String str4 = "QWE"
```

## 宣言済みのメソッドを確認

### 特定のもの

```
/method [メソッド名]
```
```console
jshell> /method addtion
|    int addtion(int,int)
```

### 宣言済みのメソッド全て

```console
/methods
```

```console
jshell> /methods
|    void methodA()
|    void methodB()
|    int addtion(int,int)
```


## 削除

変数削除

```console
/drop [変数名]
```

```console
jshell> /drop str3
|  次を削除しました: 変数 str3
```

メソッド削除

```console
/drop [メソッド名]
```

```console
jshell> /drop methodA
|  次を削除しました: メソッド methodA()
```

## import確認

```console
/import
```

```console
jshell> /import
|    import java.io.*
|    import java.math.*
|    import java.net.*
|    import java.nio.file.*
|    import java.util.*
|    import java.util.concurrent.*
|    import java.util.function.*
|    import java.util.prefs.*
|    import java.util.regex.*
|    import java.util.stream.*
```


## 入力したステートメント一覧
```
/list
```

```console
jshell> /list

   1 : String str1 = "AAAA";
   2 : String str2 = "BBBB";
   3 : int addtion(int a, int b) {
            return a + b;
       }
   4 : int i1 = 1;
```

エラーになったステートメントとかimportとか全部の場合はallオプションを付ける。

```
/list -all
```

```console
jshell> /list -all

  s1 : import java.io.*;
  s2 : import java.math.*;
  s3 : import java.net.*;
  s4 : import java.nio.file.*;
  s5 : import java.util.*;
  s6 : import java.util.concurrent.*;
  s7 : import java.util.function.*;
  s8 : import java.util.prefs.*;
  s9 : import java.util.regex.*;
 s10 : import java.util.stream.*;
   1 : String str1 = "AAAA";
   2 : String str2 = "BBBB";
   3 : int addtion(int a, int b) {
            return a + b;
       }
   4 : int i1 = 1;
  e1 : int i2 = "2"; <----★これエラーになったやつ
```

## リセット
入力したステートメントを全てクリア。起動時に戻る。

```
/reset
```

リセットした直後

```console
jshell> /list -all

  s1 : import java.io.*;
  s2 : import java.math.*;
  s3 : import java.net.*;
  s4 : import java.nio.file.*;
  s5 : import java.util.*;
  s6 : import java.util.concurrent.*;
  s7 : import java.util.function.*;
  s8 : import java.util.prefs.*;
  s9 : import java.util.regex.*;
 s10 : import java.util.stream.*;
 ```
 
### コマンド一覧
 
 [jshellコマンド](https://docs.oracle.com/javase/jp/15/docs/specs/man/jshell.html)
 
## タブ補完
 
 コマンドやメソッド名は最後まで入力しなくても、何文字か入力してタブキーを押下すると補完してくれる。複数候補がある場合は、候補を表示してくれる。
 
 ```console
jshell>  /l[タブキー]

jshell>  /list
```

候補が複数ある場合

```console
jshell> me
methodA()   methosB()
jshell> metho
```

# おまけ

## jdbc疎通確認してみた

AWS Aurora Postgresqlへ接続。

### ドライバークラスパス設定
```console
jshell --class-path ./lib/postgresql-42.4.0.jar
```

### 疎通確認

正常に接続できた時。

```console
jshell> var conn = java.sql.DriverManager.getConnection("jdbc:postgresql://sp-work.cluster-xxxxxxxxxxx.ap-northeast-1.r
ds.amazonaws.com:5432/postgres", "user", "xxxxxx");
conn ==> org.postgresql.jdbc.PgConnection@74e52303
```

hostが見つからなくて失敗した時。

```console
jshell> var conn = java.sql.DriverManager.getConnection("jdbc:postgresql://sp-work.hoge.com:5432/postgres", "postgres",
"xxxxxx");
|  例外org.postgresql.util.PSQLException: 接続試行は失敗しました。
|        at ConnectionFactoryImpl.openConnectionImpl (ConnectionFactoryImpl.java:331)
|        at ConnectionFactory.openConnection (ConnectionFactory.java:49)
|        at PgConnection.<init> (PgConnection.java:223)
|        at Driver.makeConnection (Driver.java:402)
|        at Driver.connect (Driver.java:261)
|        at DriverManager.getConnection (DriverManager.java:683)
|        at DriverManager.getConnection (DriverManager.java:230)
|        at do_it$Aux (#14:1)
|        at (#14:1)
|  原因: java.net.UnknownHostException: sp-work.hoge.com
|        at NioSocketImpl.connect (NioSocketImpl.java:564)
|        at SocksSocketImpl.connect (SocksSocketImpl.java:327)
|        at Socket.connect (Socket.java:633)
|        at PGStream.createSocket (PGStream.java:241)
|        at PGStream.<init> (PGStream.java:98)
|        at ConnectionFactoryImpl.tryConnect (ConnectionFactoryImpl.java:109)
|        at ConnectionFactoryImpl.openConnectionImpl (ConnectionFactoryImpl.java:235)
|        ...
```

閉じる。

```console
jshell> conn.close()
jshell>
```