JShell: 2. 스니펫(Snippets)

JShell: 2. 스니펫(Snippets)

이 문서는 Java9의 오라클 공식 문서 중 JShell 사용자 가이드의 한글화 문서입니다. 원문 정보는 다음과 같습니다.

JShell 사용자 가이드의 한글화 문서는 다음과 같이 구성됩니다.


JShell 사용자 가이드: 2. 스니펫


JShell에서 자바 문(statements), 정의 문(변수, 메서드, 클래스 둥), 임포트 문 및 식(expression)을 실행할 수 있습니다. JShell이 처리할 수 있는 이런 코드 조각을 스니펫(snippet)이라고 합니다.

  • 문서 구성
    • 스니펫 실행하기
    • 정의 변경
    • Forward References
    • 예외(Exceptions)
    • 스니펫 텝 자동 완성
    • 스니펫 변환

스니펫 실행하기

JShell에 자바 코드 스니펫을 입력하면 즉시 평가됩니다. 그리고 평가 결과, 수행 액션 및 발생 에러에 대한 피드백이 바로 출력됩니다. 이 절에서 소개하는 예제를 직접 JShell에 테스트해 보시기 바랍니다.

최대한 많은 피드백을 출력하기 위해서, verbose 옵션(-v)을 추가하여 JShell을 시작합니다.

% jshell -v
|  Welcome to JShell -- Version 9
|  For an introduction type: /help intro

프롬프트에 다음 예제의 문(statement)을 입력하고, 출력 결과를 확인해 보시기 바랍니다.

jshell> int x = 45
x ==> 45
|  created variable x : int

문을 입력하며, 바로 결과가 출력됩니다. 이 결과를 다음과 같이 해석할 수 있니다. “변수 x는 값 45를 갖는다.” JShell이 verbose 모드로 실행되었기 때문에, 수행된 액션에 대한 부가 설명도 출력됩니다. 부가 정보 메시지는 수직 막대(|) 문자로 시작합니다. 이 메시지에서 생성된 변수명과 변수 타입 정보를 확인할 수 있습니다.

입력 완료된 스니펫 끝에 세미콜론이 없다면, 내부적으로 문(statement) 종료를 표시하는 세미콜론(;) 문자가 자동 추가됩니다.

처리 결과를 저장할 변수를 지정하지 않은 식(expression)이 입력되면, 다음에 값을 참조하는 목적으로 스크래치 변수1가 생성됩니다. 그리고 그 스크래치 변수에 실행된 식의 결과가 저장됩니다. 다음 예제는 식과 메서드 실행 결과를 스크래치 변수에 저장하는 것을 설명합니다. 그리고 이 예제에서 하나의 스니펫이 여러줄로 완성할 때 사용되는 연속 프롬프트 (…>)를 확인 할 수 있습니다.

jshell> 2 + 2
$3 ==> 4
|  created scratch variable $3 : int

jshell> String twice(String s) {
   ...>    return s + s;
   ...> }
|  created method twice(String)

jshell> twice("Ocean")
$5 ==> "OceanOcean"
|  created scratch variable $5 : String

정의 변경

코드를 실행하다보면 변수, 메서드 및 클래스가 개발자의 의도와 다르게 잘못 정의될 수 있습니다. JShell에서 이런 문제는 정의를 새로 입력하는 것으로 쉽게 해결 됩니다. 새로 입력된 정의는 이전 정의를 덮어씁니다.

변수, 메서드 및 클래스의 정의를 변경해야 한다면, 단순히 새로운 정의를 입력하면 됩니다. 예를 들어서 앞에 “스니펫 실행하기” 절에서 정의한 twice 메서드는 다음 예제를 실행하면 새로운 정의로 변경됩니다.

jshell> String twice(String s) {
   ...>    return "Twice:" + s;
   ...> }
|  modified method twice(String)

jshell> twice("thing")
$7 ==> "Twice:thing"
|  created scratch variable $7 : String

앞에서 “생성된 메서드(created method)“로 표시된 부분이 이번 피드백에서는 “수정된 메서드(modified method)“로 표시됩니다. 이 메시지는 정의는 변경되었지만, 변경된 메서드가 이전과 같은 서명(signature)을 가지므로, 기존의 메서드 사용법이 여전히 유효합니다.

호화되지 않는 방식으로 정의를 변경할 수도 있습니다. 다음 예제에서 변수 x 타입이 int에서 String 으로 변경되었습니다.

jshell> int x = 45
x ==> 45
|  created variable x : int

jshell> String x
x ==> null
|  replaced variable x : String
|    update overwrote variable x : int

위 예제에서 변수 x의 타입 변경에 대한 피드백 메시지가 “replaced“로 표현되어 있습니다.

피드백 레벨 변경

앞에서 가장 많은 정보와 설명을 제공하는 verbose 모드로 JShell을 실행하였습니다. “/set feedback” 명령으로 피드백 양과 형식을 변경할 수 있습니다. (예 : /set feedback consice) 다른 윈도우의 코드를 복사한 후 붙여 넣기 방식으로 JShell을 사용한다면, 여러분은 프롬프트가 없고 오류 피드백 만을 제공하는 모드를 선호할 것입니다. JShell에 “/set feedback silent” 명령을 실행하면, JShell은 오류 피드백만을 출력합니다.

Forward References (사전 참조)

JShell에서 메소드를 정의할 때 아직 정의하지 않은 메서드, 변수, 클래스를 참조할 수 있습니다. 이 기능은 탐색적인 프로그래밍을 지원하기 위해서 만들어졌습니다. 프로그래밍 과정에서 이런 기능이 필요할 때가 있습니다.

예를 들어, 구의 볼륨을 구하는 메소드를 정의할 때, volume 메소드를 다음 예제와 같이 구현할 수 있습니다.

jshell> double volume(double radius) {
   ...>    return 4.0 / 3.0 * PI * cube(radius);
   ...> }
|  created method volume(double), however, it cannot be invoked until variable PI, and method cube(double) are declared

위 예제의 volume 메소드 정의는 JShell 세션에서 정상적으로 수행됩니다. 그러나, 아직은 volume 메서드가 완전하게 정의된 것이 아니라는 메시지가 출력됩니다. 이 메소드 정의는 참조될 수는 있있습니다. 그러나, 필요한 모든 요소를 정의하기 전에 이 메서드를 실행하면 실폐하고 에러 피드백을 출력합니다.

jshell> double PI = 3.1415926535
PI ==> 3.1415926535
|  created variable PI : double

jshell> volume(2)
|  attempted to call method volume(double) which cannot be invoked until method cube(double) is declared

jshell> double cube(double x) { return x * x * x; }
|  created method cube(double)
|    update modified method volume(double)

jshell> volume(2)
$5 ==> 33.510321637333334
|  created scratch variable $5 : double

필요한 모든 정의가 완료되면, volume 메서드는 정상적으로 작동합니다.

이 volume 메서드를 이용하여 “비호환 교체”를 설명하겠습니다. 다음 예제에서는 PI의 정밀도를 변경2하고, PI 객체에 새로운 값을 입력하였습니다.

jshell> BigDecimal PI = new BigDecimal("3.141592653589793238462643383")
PI ==> 3.141592653589793238462643383
|  replaced variable PI : BigDecimal
|    update modified method volume(double) which cannot be invoked until this error is corrected:
|      bad operand types for binary operator '*'
|        first type:  double
|        second type: java.math.BigDecimal
|          return 4.0 / 3.0 * PI * cube(radius);
|                 ^------------^
|    update overwrote variable PI : double

새롭게 정의된 PI 타입은 volume 메서드 정의에 사용된 타입과 호환되지 않습니다. 현재 JShell은 verbose 모드로 동작하기 때문에, PI 타입 변경에 영향을 받는 다른 요소 정보를 출력합니다. 이 예제는 비호환성을 설명하는 대표적인 사례입니다. verbose 모드는 업데이트 정보를 출력하는 유일한 사전 정의 피드백 모드(Predefined feedback mode)입니다. 다른 피드백 모드에서는 해당 코드가 실행될 때까지 어떠한 경고도 출력하지 않습니다. verbose 모드를 제외한 다른 사전 정의 피드백 모드는 실행 부하를 줄이기 위해서 업데이트 경고를 제공하지 않습니다. 모든 사전 정의 피드백 모드에서 volume 메서드를 실행하면, 다음과 같은 이슈를 출력합니다.

jshell> volume(2)
|  attempted to call method volume(double) which cannot be invoked until this error is corrected:
|      bad operand types for binary operator '*'
|        first type:  double
|        second type: java.math.BigDecimal
|          return 4.0 / 3.0 * PI * cube(radius);
|                 ^------------^

예외(Exceptions)

예외 역추적(Backtrace)에서, 피드백은 예외가 발생한 스니펫과 해당 스니펫 내부의 위치를 출력합니다.

JShell에 입력된 코드의 위치는 “#ID:line-number” 형식으로 출력됩니다. 여기서 스니펫 ID는 “/list” 명령이 제공하는 숫자입니다. “line-number“는 스니펫의 행 번호입니다. 다음 예제에서 예외가 발생한 위치는 스니펫 넘버 #1(devide 메서드)의 두 번째 행입니다.

jshell> int divide(int x, int y) {
   ...> return x / y;
   ...> }
|  created method divide(int,int)

jshell> divide(5, 0)
|  java.lang.ArithmeticException thrown: / by zero
|        at divide (#1:2)
|        at (#2:1)

jshell> /list

   1 : int divide(int x, int y) {
           return x / y;
       }
   2 : divide(5, 0)

스니펫 텝 자동 완성

스니펫을 입력할 때, 텝 키를 이용하여 코드를 자동 완성할 수 있습니다. 현재까지 입력으로 코드를 결정할 수 없는 상황에서는, 가능한 옵션을 모두 제공합니다.

다음 예제는 volume 메서드가 정의된 상태에서, 메서드 명의 첫 번째 3 개 문자(vol)를 입력한 후에 텝 키를 입력한 상태를 설명합니다.

jshell> vol<Tab>

탭 키를 입력하면 입력값이 다음과 같이 변경됩니다.

jshell> volume(

자동 완성 기능을 사용할 때 현재 입력 값을 기준으로 한 개 이상의 경우의 수를 갖는다면, 가능한 모든 코드를 출력합니다.

jshell> System.c<Tab>
class                 clearProperty(        console()             currentTimeMillis()

jshell> System.c

입력한 코드에 공통 문자가 추가되고 커서는 입력 끝부분으로 이동합니다. 아이템을 완성하기 위해 추가 문자를 입력할 수 있습니다.

메소드의 열린 괄호에서 텝 키를 입력하면 매개 변수 유형으로 완료 가능한 형태를 출력합니다.

jshell> "hello".startsWith(<Tab>
Signatures:
boolean String.startsWith(String prefix, int toffset)
boolean String.startsWith(String prefix)

<press tab again to see documentation>

jshell> "hello".startsWith(

텝 키를 다시 입력하면 첫 번째 메서드의 텍스트 버전 문서가 출력됩니다.

jshell> "hello".startsWith(<Tab>
boolean String.startsWith(String prefix, int toffset)
Tests if the substring of this string beginning at the specified index starts with the
specified prefix.

Parameters:
prefix - the prefix.
toffset - where to begin looking in this string.

Returns:
true if the character sequence represented by the argument is a prefix of the substring of this
          object starting at index toffset ; false otherwise. The result is false if toffset is
          negative or greater than the length of this String object; otherwise the result is
          the same as the result of the expression
                    this.substring(toffset).startsWith(prefix)


<press tab to see next documentation>

jshell> "hello".startsWith(

스니펫 변환

JShell은 클래스를 처음 참조할 때 필요한 클래스 임포트(import)를 쉽게 할 수 있습니다. 또한 단축키를 사용하여 식(Expression)을 변수 선언으로 간단하게 변환할 수 있습니다.

아직 가져 오지 않은 식별자를 입력한 다음에 바로 “Shift + Tab i“를 누르면 세션에 import문을 추가하는 옵션이 출력됩니다.

jshell> new JFrame<Shift+Tab i>
0: Do nothing
1: import: javax.swing.JFrame
Choice: 1
Imported: javax.swing.JFrame

jshell> new JFrame

임포트 추가 옵션이 출력되면 사용할 옵션 번호를 입력하면 됩니다. 복수의 import 옵션이 제시될 수 있습니다.

식(Expression)을 입력한 후 “Shift+Tab v“를 눌러 식(Expression) 변수 선언으로 변환할 수 있습니다. 앞에서 입력한 식은 변수 선언의 초기값이 되고, 식의 반환 타입은 변수의 타입으로 선언됩니다. 다음 예제에서 “Shift+Tab v“를 누르면, 세로 막대 (|)로 표시된 커서는 변수 이름을 입력해야 하는 곳에 위치하게 됩니다.

jshell> new JFrame("Demo") <Shift+Tab v>
jshell> JFrame | = new JFrame("Demo")

식(Expression)은 유효해야 하며 그렇지 않으면 변환 요청은 무시됩니다. 이 예제에서 변수를 변환하기 전에 JFrame의 import가 선행되어야 합니다.

앞 예제를 완료하기 위해서, 커서가 위치한 곳에 변수명 frame을 입력하고 Enter 키를 누릅니다.

jshell> JFrame frame = new JFrame("Demo")
frame ==> javax.swing.JFrame[frame0,0,0,0x0,invalid,hidden, ... tPaneCheckingEnabled=true]
|  created variable frame : JFrame

jshell>

식(Expression)의 반환 타입이 아직 import 되지 않은 경우가 있습니다. 이런 상황에서, “Shift+Tab v“을 누르면 import 옵션과 변수 생성 기능 모두가 제공됩니다.

jshell> frame.getGraphics() <Shift+Tab v>
0: Do nothing
1: Create variable
2: import: java.awt.Graphics. Create variable
Choice: 2
Imported: java.awt.Graphics

jshell> Graphics | = frame.getGraphics()

위 예제를 완료하려면, 커서가 위치한 곳에 변수 이름 gc를 입력하고 Enter 키를 누릅니다.

jshell> Graphics gc = frame.getGraphics()
|  created variable gc : JFrame

jshell>

  1. [역자주]스크래치 변수(Scratch variable) - 특별한 의미를 갖지 않는 변수로, 값을 잠시 보관하는 용도로 만들어진 임시 변수로, 대개 보유한 값이 얼마 후에 의미가 없어지거나 삭제됨. [return]
  2. [역자주]“정밀도 변경”이란 변수 타입의 변경을 의미하고, “새로운 값을 입력”은 객체 생성 후 할당을 의미합니다. [return]
작성자: 김태완
김태완 avatar
작성자: 김태완
1999년 부터 Java, Framework, Middleware, SOA, DB Replication, Cache, CEP, NoSQL, Big Data, Cloud를 키워드로 살아왔습니다. 현재는 한국오라클 빅데이터 팀 소속으로 빅데이터와 Machine Learning을 중점에 두고 있습니다. 최근에는 Deep Learning을 열공 중입니다.
E-mail: taewanme@gmail.com