Java 9 - JShell

Wersja z dnia 2017-08-16

JShell to Java shell - program dostępny w Java 9, który pozwala na pisanie i uruchamianie kodu w Java wg. schematu REPL

Program JShell działa w trybie tekstowym. Umożliwia szybkie testowanie pomysłów i sprawdzanie wyniku pracy naszego kodu bez konieczności jego ręcznej kompilacji i uruchamiania - procesem tym zajmuje się JShell.

Uruchomienie

JShell uruchamiany jest za pomocą polecenia:

$ katalogZjdk9/bin/jshell 
|  Welcome to JShell -- Version 9
|  For an introduction type: /help intro

jshell> _

Pierwszy "program"

Pierwszy "program" (a właściwie fragment kodu/wstawka ang. snippet) jest bardzo prosty, pokazuje jednak siłę nowego narzędzia. Kodu, który ma wykonać pracę, nie trzeba opakowywać w klasy czy metody. Wystarczy wpisać:

jshell> System.out.println( "Czesc!" )[Enter]
Czesc!

jshell> _ 

Od razu mamy efekt...

System pomocy.

A teraz pierwszy program napisany z pomocą klawisza [Tab]. Ale najpierw mały pokaz możliwości.

jshell> Sy[Tab]
SyncFailedException   SynchronousQueue      System                

jshell> Sys[Tab]
System   

jshell> System[Tab]
System   

Signatures:
java.lang.System

<press tab again to see documentation>

jshell> System[Tab]
java.lang.System
The System class contains several useful class fields and methods.It cannot be instantiated.
Among the facilities provided by the System class are standard input, standard output, and
error output streams; access to externally defined properties and environment variables; a
means of loading files and libraries; and a utility method for quickly copying a portion of an
array.

jshell> _

A teraz obiecany program:

jshell> System.[Tab]
Logger                 LoggerFinder           arraycopy(             class                  
clearProperty(         console()              currentTimeMillis()    err                    
exit(                  gc()                   getLogger(             getProperties()        
getProperty(           getSecurityManager()   getenv(                identityHashCode(      
in                     inheritedChannel()     lineSeparator()        load(                  
loadLibrary(           mapLibraryName(        nanoTime()             out                    
runFinalization()      runFinalizersOnExit(   setErr(                setIn(                 
setOut(                setProperties(         setProperty(           setSecurityManager(    

jshell> System.o[Tab]
out   

jshell> System.out.[Tab]
append(        checkError()   close()        equals(        flush()        format(        
getClass()     hashCode()     notify()       notifyAll()    print(         printf(        
println(       toString()     wait(          write(         

jshell> System.out.print[Tab]
print(     printf(    println(   

jshell> System.out.printl[Tab]
println(   

jshell> System.out.println("Czesc")
Czesc

A teraz coś o /help.

Aby uzyskać pomoc w kwestii wbudowanych w jshell poleceń wpisujemy po prostu:

/help

Pojawia się lista dostępnych poleceń z krótkim ich omówieniem. Na początek warto poznać:

Przykład użycia:

jshell> /? /list
|  
|  /list
|  
|  Show the source of snippets, prefaced with the snippet id.
|  
|  /list
|  	List the currently active snippets of code that you typed or read with /open
|  
|  /list -start
|  	List the automatically evaluated start-up snippets
|  
|  /list -all
|  	List all snippets including failed, overwritten, dropped, and start-up
|  
|  /list 
|  	List snippets with the specified name (preference for active snippets)
|  
|  /list 
|  	List the snippet with the specified snippet id

Coś bardziej ambitnego

Proste rachunki z zastosowaniem JShell
jshell> 2+2
$1 ==> 4

jshell> $1
$1 ==> 4

jshell> $1+2
$3 ==> 6

jshell> /list

   1 : 2+2
   2 : $1
   3 : $1+2

jshell> /list $1

   1 : 2+2

Jak widać wyniki naszej pracy są dostępne poprzez automatycznie tworzone zmienne (tutaj $1 i $3).

Policzmy teraz obwód koła o promieniu $1. Aby tego dokonać potrzebna jest wartość Π. Oczywiście można ją wpisać ręcznie. Lepiej jednak użyć stałej dostępnej w Java:

jshell> Math.PI[Tab]
PI   

Signatures:
Math.PI:double

<press tab again to see documentation>

jshell> Math.PI[Tab]
Math.PI:double
The double value that is closer than any other to pi , the ratio of the circumference of a
circle to its diameter.

jshell> 2*Math.PI*$1 
$4 ==> 25.132741228718345

Może pojawić się pytanie: jakie zmienne pojawiły się w trakcie pracy z JShell i jakiego są one typu? Nic prostrzego. Można się o tym dowiedzieć za pomocą wbudowanego polecenia /vars.

jshell> /vars
|    int $1 = 4
|    int $3 = 6
|    double $4 = 25.132741228718345

jshell> /vars $4
|    double $4 = 25.132741228718345

Własne zmienne

Aby utworzyć własną zmienną musimy podać jej nazwę poprzedzoną nazwą typu. Zmiennych można potem używać w obliczeniach.

jshell> int a = 10
a ==> 10

jshell> a + 1
$2 ==> 11

jshell> /!
a + 1
$3 ==> 11

Przy okazji: wbudowane w JShell polecenie /! pozwala na powtórne wykonanie ostatniego fragmentu kodu.

Uruchomienie wybranego fragmentu kodu.

Fragmenty kodu, które wpisujemy do JShell otrzymują kolejne identyfikatory liczbowe. Można je poznać wpisując polecnie /list:

jshell> int a = 10
a ==> 10

jshell> a + 1
$2 ==> 11

jshell> a + 2
$3 ==> 12

jshell> /list

   1 : int a = 10;
   2 : a + 1
   3 : a + 2

Jeśli chcemy uruchmić wybrany fragment kodu używamy wbudowanego w JShell polecenia /<id>, gdzie <id>, to numer fragmentu kodu, który nas interesuje. Np.:

jshell> /3
a + 2
$4 ==> 12

Ciekawostka.

Istnieje możliwość zadeklarowania zmiennej referencyjnej, o typie, który nie jest jeszcze znany (utworzony). Zmienna istnieje, ale nie można jej używać...

jshell> Kot k
|  created variable k, however, it cannot be referenced until class Kot is declared

Metody.

W Java metody tworzone są wewnątrz klas czy interfejsów (od Java 8). W JShell możemy utworzyć metodę niejako wprost.

jshell> void poleKwadratu( int bok ) {
   ...> System.out.println( "Pole kwadratu o boku " + bok + " to " + 4 * bok );
   ...> }
|  created method poleKwadratu(int)

jshell> poleKwadratu(5)
Pole kwadratu o boku 5 to 20

Działa... tylko, że w metodzie jest błąd - zamiast pola liczony jest obwód. Jak to poprawić?

jshell> /edit poleKwadratu 
|  modified method poleKwadratu(int)

Wbudowane polecenie /edit otwiera prosty edytor Ascii - w nim znajdujemy kod naszej metody. Wprowadzamy poprawki i klikamy exit.

jshell> poleKwadratu(5)
Pole kwadratu o boku 5 to 25

Od razu lepiej. Jeśli wbudowany edytor nam nie odpowiada, to możemy wskazać inny.

/set editor gedit

Od tej chwili polecenie /edit uruchomi edytor o nazwie gedit.

Zapis i odczyt

Zapis naszej pracy można zlecić za pomocą polecenia /save, po którym podaje się nazwę pliku, w którym zostanie ona zapisana. Odczyt możliwy jest za pomocą polecenia /open.

Klasy

A teraz przykład prostej klasy. Zaczynamy od utworzenia referencji do obiektu klasy Kot, a potem tworzymy klasę z jedną metodą mrr.

jshell> Kot kot;
|  created variable kot, however, it cannot be referenced until class Kot is declared

jshell> class Kot {
   ...> void mrr() {
   ...> System.out.println("Mrrrrr....");
   ...> }
   ...> }
|  created class Kot
|    update replaced variable kot, reset to null

jshell> kot = new Kot()
kot ==> Kot@42dafa95

jshell> kot.mrr()
Mrrrrr....

Jak widać, po utworzeniu klasy Kot JShell przypomniał sobie o zminnej kot i zainicjował ją wartością null.

Wyjątki

Sprawdzmy co się stanie gdy wykonamy niestatyczną metodę posługując się referencją, w której jest null.

jshell> kot = null
kot ==> null

jshell> kot.mrr()
|  java.lang.NullPointerException thrown: 
|        at (#6:1)

jshell> /list

   1 : Kot kot;
   2 : class Kot {
       void mrr() {
       System.out.println("Mrrrrr....");
       }
       }
   3 : kot = new Kot()
   4 : kot.mrr()
   5 : kot = null
   6 : kot.mrr()

JShell zgłasza problem i działa nadal. Wskazuje na numer fragmentu kodu, w którym pojawił się wyjątek.