데이터 타입

데이터 타입 (Data Types)

Groovy에서 쓰는 기본 데이터 타입들을 짚어 볼게요. 문자열, 숫자, boolean이 어떻게 정의되고 동작하는지가 이 문서의 중심이에요. 특히 문자열은 여러 표기법이 있으니 하나씩 따라와 주세요. 자바를 알고 있다면 많은 부분이 익숙하겠지만, Groovy만의 편리한 표기들도 눈여겨볼 만해요.

출처: Groovy 공식 문서 - Syntax (Data types)

본문

4. 문자열 (Strings)

문자 리터럴은 문자열(string)이라고 부르는 문자들의 연결로 표현돼요. Groovy는 java.lang.String 객체를 만들 수 있고, 다른 프로그래밍 언어에서 '보간 문자열(interpolated strings)'이라고도 부르는 GString(groovy.lang.GString)도 만들 수 있어요.

4.1. 작은따옴표 문자열 (Single-quoted string)

작은따옴표 문자열은 작은따옴표로 감싼 문자들의 연속이에요.

'a single-quoted string'

Note: 작은따옴표 문자열은 그냥 java.lang.String이고, 보간(interpolation)을 지원하지 않아요.

4.2. 문자열 연결 (String concatenation)

Groovy의 모든 문자열은 + 연산자로 연결할 수 있어요.

assert 'ab' == 'a' + 'b'

4.3. 작은따옴표 세 개짜리 문자열 (Triple-single-quoted string)

작은따옴표 세 개짜리 문자열은 작은따옴표 세 개로 감싼 문자들의 연속이에요.

'''a triple-single-quoted string'''

Note: 작은따옴표 세 개짜리 문자열은 그냥 java.lang.String이고, 보간을 지원하지 않아요.

작은따옴표 세 개짜리 문자열은 여러 줄에 걸칠 수 있어요. 문자열을 여러 조각으로 나누거나 연결하거나 개행 이스케이프 문자를 쓰지 않고도, 문자열의 내용이 줄 경계를 넘어갈 수 있어요.

def aMultilineString = '''line one
line two
line three'''

코드가 들여쓰기되어 있다면, 예를 들어 클래스의 메서드 본문에 있다면, 문자열에도 그 들여쓰기 공백이 포함돼요. Groovy 개발 키트(GDK)에는 이런 들여쓰기를 제거하는 메서드가 있는데, String#stripIndent()와, 문자열 앞에서 제거할 텍스트를 식별하는 구분 문자를 받는 String#stripMargin()이 있어요.

다음처럼 문자열을 만들면:

def startingAndEndingWithANewline = '''
line one
line two
line three
'''

결과 문자열의 첫 번째 문자가 개행 문자라는 걸 알 수 있어요. 그 문자는 백슬래시로 개행을 이스케이프해서 제거할 수 있어요.

def strippedFirstNewline = '''\
line one
line two
line three
'''

assert !strippedFirstNewline.startsWith('\n')
4.3.1. 특수 문자 이스케이프 (Escaping special characters)

문자열 리터럴이 끝나는 것을 막으려면 백슬래시 문자로 작은따옴표를 이스케이프할 수 있어요.

'an escaped single quote: \' needs a backslash'

이스케이프 문자 자체는 백슬래시 두 개로 이스케이프할 수 있어요.

'an escaped escape character: \\ needs a double backslash'

백슬래시를 이스케이프 문자로 사용하는 특수 문자도 몇 가지 있어요.

이스케이프 시퀀스 문자
\b 백스페이스 (backspace)
\f 폼피드 (formfeed)
\n 개행 (newline)
\r 캐리지 리턴 (carriage return)
\s 공백 하나 (single space)
\t 탭 (tabulation)
\\ 백슬래시 (backslash)
\' 작은따옴표 문자열 안의 작은따옴표 (작은따옴표 3개, 큰따옴표 문자열에서는 선택)
\" 큰따옴표 문자열 안의 큰따옴표 (큰따옴표 3개, 작은따옴표 문자열에서는 선택)

나중에 다루는 다른 종류의 문자열에서 이스케이프에 관한 더 많은 내용을 보게 될 거예요.

4.3.2. 유니코드 이스케이프 시퀀스 (Unicode escape sequence)

키보드에 없는 문자는 유니코드 이스케이프 시퀀스를 쓸 수 있어요. 백슬래시 다음에 u, 그리고 16진수 4자리로 표현해요. 예를 들어 유로 통화 기호는 이렇게 나타낼 수 있어요.

'The Euro currency symbol: \u20AC'

4.4. 큰따옴표 문자열 (Double-quoted string)

큰따옴표 문자열은 큰따옴표로 감싼 문자들의 연속이에요.

"a double-quoted string"

Note: 큰따옴표 문자열은 보간 표현식이 없으면 그냥 java.lang.String이지만, 보간이 있으면 groovy.lang.GString 인스턴스예요.

Note: 큰따옴표를 이스케이프하려면 백슬래시 문자를 쓸 수 있어요: "A double quote: \"".

4.4.1. 문자열 보간 (String interpolation)

작은따옴표 문자열과 작은따옴표 3개짜리 문자열을 제외한 모든 문자열 리터럴에서는 어떤 Groovy 표현식이든 보간할 수 있어요. 보간은 문자열이 평가될 때 문자열 안의 자리 표시자(placeholder)를 그 값으로 치환하는 작업이에요. 자리 표시자 표현식은 ${}로 감싸요. 모호함이 없는 점이 붙은 표현식(dotted expression)에서는 중괄호를 생략할 수 있어요. 즉 그런 경우에는 $ 접두어만 사용하면 돼요.

GString이 String을 받는 메서드에 전달되면, 자리 표시자 안의 표현식 값은 그 문자열 표현으로 평가되고(그 표현식에 toString()을 호출해서), 결과 String이 메서드에 전달돼요.

여기서는 지역 변수를 참조하는 자리 표시자가 있는 문자열을 볼게요.

def name = 'Guillaume' // a plain string
def greeting = "Hello ${name}"

assert greeting.toString() == 'Hello Guillaume'

어떤 Groovy 표현식이든 유효해요. 산술 표현식이 있는 이 예시에서 볼 수 있듯이요.

def sum = "The sum of 2 and 3 equals ${2 + 3}"
assert sum.toString() == 'The sum of 2 and 3 equals 5'

Note: ${} 자리 표시자 안에는 표현식뿐 아니라 문장(statement)도 허용돼요. 다만 문장의 값은 그냥 null이에요. 그래서 그 자리 표시자 안에 여러 문장을 넣으면, 마지막 문장이 삽입할 의미 있는 값을 어떻게든 반환해야 해요. 예를 들어 "The sum of 1 and 2 is equal to ${def a = 1; def b = 2; a + b}"처럼 쓰면 지원되고 예상대로 동작하긴 하지만, GString 자리 표시자 안에서는 단순한 표현식을 쓰는 게 보통 좋은 습관이에요.

${} 자리 표시자에 더해, 점이 붙은 표현식 앞에는 홀로 딸린 $ 기호를 쓸 수도 있어요.

def person = [name: 'Guillaume', age: 36]
assert "$person.name is $person.age years old" == 'Guillaume is 36 years old'

다만 a.b, a.b.c 같은 형태의 점 표현식만 유효해요. 메서드 호출 같은 괄호를 포함한 표현식, 클로저의 중괄호, 프로퍼티 표현식의 일부가 아닌 점, 또는 산술 연산자가 있는 표현식은 유효하지 않아요.

다음과 같은 숫자 변수 정의가 있다고 할게요.

def number = 3.14

다음 문장은 groovy.lang.MissingPropertyException을 던져요. Groovy가 그 숫자의 toString 프로퍼티에 접근하려 한다고 생각하는데, 그런 프로퍼티는 존재하지 않거든요.

shouldFail(MissingPropertyException) {
    println "$number.toString()"
}

Note: "$number.toString()"은 파서가 "${number.toString}()"으로 해석한다고 생각하면 돼요.

마찬가지로 표현식이 모호하면 중괄호를 유지해야 해요.

String thing = 'treasure'
assert 'The x-coordinate of the treasure is represented by treasure.x' ==
    "The x-coordinate of the $thing is represented by $thing.x"   // <= Not allowed: ambiguous!!
assert 'The x-coordinate of the treasure is represented by treasure.x' ==
        "The x-coordinate of the $thing is represented by ${thing}.x"  // <= Curly braces required

GString에서 $${} 자리 표시자를 보간 없이 그대로 나타내고 싶다면, 백슬래시 문자로 달러 기호를 이스케이프하면 돼요.

assert '$5' == "\$5"
assert '${name}' == "\${name}"
4.4.2. 클로저 표현식 보간의 특수한 경우 (Special case of interpolating closure expressions)

지금까지 ${} 자리 표시자 안에 임의의 표현식을 보간할 수 있다는 걸 보았는데, 클로저 표현식을 위한 특수한 경우와 표기법이 있어요. 자리 표시자에 화살표 ${->}가 들어 있으면, 그 표현식은 실제로 클로저 표현식이에요. 앞에 달러가 붙은 클로저라고 생각하면 돼요.

def sParameterLessClosure = "1 + 2 == ${-> 3}" (1)
assert sParameterLessClosure == '1 + 2 == 3'

def sOneParamClosure = "1 + 2 == ${ w -> w << 3}" (2)
assert sOneParamClosure == '1 + 2 == 3'
  • (1) 클로저는 인자를 받지 않는 파라미터 없는 클로저예요.
  • (2) 여기서 클로저는 java.io.StringWriter 인자를 하나 받고, << leftShift 연산자로 내용을 덧붙일 수 있어요. 어떤 경우든 두 자리 표시자 모두 내장된 클로저예요.

겉보기에는 보간할 표현식을 정의하는 더 장황한 방식처럼 보일 수 있는데, 클로저는 단순 표현식보다 흥미로운 장점이 있어요. 바로 지연 평가(lazy evaluation)예요. 다음 예시를 볼게요.

def number = 1 (1)
def eagerGString = "value == ${number}"
def lazyGString = "value == ${ -> number }"

assert eagerGString == "value == 1" (2)
assert lazyGString ==  "value == 1" (3)

number = 2 (4)
assert eagerGString == "value == 1" (5)
assert lazyGString ==  "value == 2" (6)
  • (1) 1을 담은 number 변수를 정의하고, 두 GString 안에 보간해요. eagerGString에서는 표현식으로, lazyGString에서는 클로저로요.
  • (2) 결과 문자열이 eagerGString에 대해 1이라는 같은 문자열 값을 담고 있을 거라 기대해요.
  • (3) lazyGString도 마찬가지예요.
  • (4) 그다음 변수 값을 새 숫자로 바꿔요.
  • (5) 단순한 보간 표현식은 그 값이 GString을 만드는 시점에 실제로 묶여요.
  • (6) 하지만 클로저 표현식은 GString이 String으로 강제 변환될 때마다 호출돼서, 새 숫자 값을 담은 갱신된 문자열을 만들어요.

Note: 파라미터를 두 개 이상 받는 내장 클로저 표현식은 런타임에 예외를 만들어요. 클로저는 파라미터가 0개나 1개인 것만 허용돼요.

4.4.3. 자바와의 호환성 (Interoperability with Java)

메서드(자바든 Groovy든 구현됐든)가 java.lang.String을 기대하는데 우리가 groovy.lang.GString 인스턴스를 전달하면, GString의 toString() 메서드가 자동으로 투명하게 호출돼요.

String takeString(String message) {         (4)
    assert message instanceof String        (5)
    return message
}

def message = "The message is ${'hello'}"   (1)
assert message instanceof GString           (2)

def result = takeString(message)            (3)
assert result instanceof String
assert result == 'The message is hello'
  • (1) GString 변수를 만들어요.
  • (2) GString의 인스턴스인지 다시 확인해요.
  • (3) 그 GString을 String을 파라미터로 받는 메서드에 전달해요.
  • (4) takeString() 메서드의 시그니처가 자신의 유일한 파라미터가 String이라고 명시하고 있어요.
  • (5) 파라미터가 실제로 GString이 아니라 String인지도 확인해요.
4.4.4. GString과 String의 hashCode (GString and String hashCodes)

보간 문자열은 평범한 자바 문자열 대신 쓸 수 있지만, 한 가지 특별한 점에서 문자열과 달라요. 바로 hashCode가 다르다는 거예요. 평범한 자바 문자열은 불변(immutable)이지만, GString의 결과 String 표현은 보간 값에 따라 달라질 수 있어요. 같은 결과 문자열이라도 GString과 String의 hashCode는 같지 않아요.

assert "one: ${1}".hashCode() != "one: 1".hashCode()

GString과 String이 서로 다른 hashCode 값을 가지므로, GString을 Map의 키로 쓰는 것은 피해야 해요. 특히 GString 대신 String으로 연관된 값을 꺼내려고 할 때 더요.

def key = "a"
def m = ["${key}": "letter ${key}"]     (1)

assert m["a"] == null                   (2)
  • (1) 맵이 키가 GString인 초기 쌍으로 만들어져요.
  • (2) String 키로 값을 꺼내려 하면 찾지 못할 거예요. String과 GString은 hashCode 값이 서로 다르기 때문이에요.

4.5. 큰따옴표 3개짜리 문자열 (Triple-double-quoted string)

큰따옴표 3개짜리 문자열은 큰따옴표 문자열처럼 동작하면서, 작은따옴표 3개짜리 문자열처럼 여러 줄이 된다는 점이 추가돼요.

def name = 'Groovy'
def template = """
    Dear Mr ${name},

    You're the winner of the lottery!

    Yours sincerly,

    Dave
"""

assert template.toString().contains('Groovy')

Note: 큰따옴표 3개짜리 문자열에서는 큰따옴표도 작은따옴표도 이스케이프할 필요가 없어요.

4.6. 슬래시 문자열 (Slashy string)

평범한 따옴표 문자열에 더해, Groovy는 슬래시 문자열(slashy string)을 제공해요. 여는 구분 문자와 닫는 구분 문자 모두 /를 사용해요. 슬래시 문자열은 정규식과 패턴을 정의할 때 특히 유용한데, 백슬래시를 이스케이프할 필요가 없기 때문이에요.

슬래시 문자열 예시:

def fooPattern = /.*foo.*/
assert fooPattern == '.*foo.*'

앞 슬래시만 백슬래시로 이스케이프하면 돼요.

def escapeSlash = /The character \/ is a forward slash/
assert escapeSlash == 'The character / is a forward slash'

슬래시 문자열은 여러 줄이 될 수 있어요.

def multilineSlashy = /one
    two
    three/

assert multilineSlashy.contains('\n')

슬래시 문자열은 GString을 정의하는 또 다른 방법이지만 이스케이프 규칙이 다르다고 생각하면 돼요. 그래서 보간을 지원해요.

def color = 'blue'
def interpolatedSlashy = /a ${color} car/

assert interpolatedSlashy == 'a blue car'
4.6.1. 특수한 경우 (Special cases)

빈 슬래시 문자열은 슬래시 두 개로 표현할 수 없어요. Groovy 파서가 그걸 줄 주석(line comment)으로 이해하기 때문이에요. 그래서 다음 assert는 실제로 컴파일되지 않아요. 종료되지 않은 문장처럼 보이거든요.

assert '' == //

슬래시 문자열은 주로 정규식을 더 쉽게 만들려고 설계되었기 때문에, $()$5처럼 GString에서는 오류인 몇 가지가 슬래시 문자열에서는 동작해요. 백슬래시 이스케이프가 필요 없다는 걸 기억하세요. 다르게 생각하면 사실 이스케이프가 지원되지 않는 것이라고 할 수도 있어요. 슬래시 문자열 /\\t/는 탭을 담지 않고, 백슬래시 다음에 문자 't'를 담아요. 이스케이프는 슬래시 문자에만 허용돼요. 즉 /\\/folder/는 '/folder'를 담은 슬래시 문자열이 돼요. 슬래시 이스케이프의 결과로, 슬래시 문자열은 백슬래시로 끝날 수 없어요. 그러면 슬래시 문자열 종결자가 이스케이프되거든요. 대신 /ends with slash ${'\\\\'}/라는 특별한 트릭을 쓸 수 있어요. 하지만 그런 경우 슬래시 문자열을 쓰지 않는 게 최선이에요.

4.7. 달러 슬래시 문자열 (Dollar slashy string)

달러 슬래시 문자열은 여는 $/와 닫는 /$로 구분되는 여러 줄 GString이에요. 이스케이프 문자는 달러 기호이고, 다른 달러나 앞 슬래시를 이스케이프할 수 있어요. 달러와 앞 슬래시 문자의 이스케이프는 그 문자의 특수 사용과 충돌이 생기는 곳에서만 필요해요. $foo 문자는 보통 GString 자리 표시자를 나타내므로, 그 네 글자를 달러 슬래시 문자열에 넣으려면 달러를 이스케이프하면 돼요. 즉 $$foo처럼요. 마찬가지로 문자열에 나타내고 싶다면 달러 슬래시 닫는 구분 문자를 이스케이프해야 해요. 예시를 몇 가지 볼게요:

def name = "Guillaume"
def date = "April, 1st"

def dollarSlashy = $/
    Hello $name,
    today we're ${date}.

    $ dollar sign
    $$ escaped dollar sign
    \ backslash
    / forward slash
    $/ escaped forward slash
    $$$/ escaped opening dollar slashy
    $/$$ escaped closing dollar slashy
/$

assert [
    'Guillaume',
    'April, 1st',
    '$ dollar sign',
    '$ escaped dollar sign',
    '\\ backslash',
    '/ forward slash',
    '/ escaped forward slash',
    '$/ escaped opening dollar slashy',
    '/$ escaped closing dollar slashy'
].every { dollarSlashy.contains(it) }

달러 슬래시 문자열은 슬래시 문자열 이스케이프 규칙의 일부 한계를 극복하려고 만들어졌어요. 이스케이프 규칙이 문자열 내용에 맞을 때(보통 이스케이프하고 싶지 않은 슬래시가 있을 때) 쓰면 돼요.

4.8. 문자열 요약 표 (String summary table)

문자열 이름 문자열 문법 보간 여러 줄 이스케이프 문자
Single-quoted '…' \
Triple-single-quoted '''…''' \
Double-quoted "…" \
Triple-double-quoted """…""" \
Slashy /…/ \
Dollar slashy $/…/$ $

4.9. 문자 (Characters)

자바와 달리 Groovy에는 명시적인 문자 리터럴이 없어요. 하지만 Groovy 문자열을 실제 문자로 만드는 것을 명시할 수 있는 방법이 세 가지 있어요.

char c1 = 'A' (1)
assert c1 instanceof Character

def c2 = 'B' as char (2)
assert c2 instanceof Character

def c3 = (char)'C' (3)
assert c3 instanceof Character
  • (1) 문자를 담는 변수를 선언할 때 char 타입을 명시해서 명시적으로 만드는 방법.
  • (2) as 연산자로 타입을 강제 변환하는 방법.
  • (3) char로 캐스팅하는 방법.

Note: 첫 번째 방법(1)은 문자가 변수에 담길 때 유용하고, 나머지 두 방법(2와 3)은 char 값이 메서드 호출의 인자로 전달되어야 할 때 더 유용해요.

5. 숫자 (Numbers)

Groovy는 자바의 평범한 Number 타입에 기반한 여러 종류의 정수 리터럴과 소수 리터럴을 지원해요.

5.1. 정수 리터럴 (Integral literals)

정수 리터럴 타입은 자바와 같아요.

  • byte
  • char
  • short
  • int
  • long
  • java.math.BigInteger

다음 선언으로 이런 타입의 정수를 만들 수 있어요.

// primitive types
byte  b = 1
char  c = 2
short s = 3
int   i = 4
long  l = 5

// infinite precision
BigInteger bi =  6

def 키워드로 선택적 타입 지정을 사용하면 정수의 타입이 달라져요. 그 숫자를 담을 수 있는 타입의 용량에 맞춰 적응하거든요. 양수부터 볼게요.

def a = 1
assert a instanceof Integer

// Integer.MAX_VALUE
def b = 2147483647
assert b instanceof Integer

// Integer.MAX_VALUE + 1
def c = 2147483648
assert c instanceof Long

// Long.MAX_VALUE
def d = 9223372036854775807
assert d instanceof Long

// Long.MAX_VALUE + 1
def e = 9223372036854775808
assert e instanceof BigInteger

음수도 마찬가지예요.

def na = -1
assert na instanceof Integer

// Integer.MIN_VALUE
def nb = -2147483648
assert nb instanceof Integer

// Integer.MIN_VALUE - 1
def nc = -2147483649
assert nc instanceof Long

// Long.MIN_VALUE
def nd = -9223372036854775808
assert nd instanceof Long

// Long.MIN_VALUE - 1
def ne = -9223372036854775809
assert ne instanceof BigInteger
5.1.1. 10진법이 아닌 다른 표현 (Alternative non-base 10 representations)

숫자는 이진법, 팔진법, 십육진법, 십진법으로도 표현할 수 있어요.

이진 리터럴 (Binary literal)

이진수는 0b 접두어로 시작해요.

int xInt = 0b10101111
assert xInt == 175

short xShort = 0b11001001
assert xShort == 201 as short

byte xByte = 0b11
assert xByte == 3 as byte

long xLong = 0b101101101101
assert xLong == 2925l

BigInteger xBigInteger = 0b111100100001
assert xBigInteger == 3873g

int xNegativeInt = -0b10101111
assert xNegativeInt == -175
팔진 리터럴 (Octal literal)

팔진수는 전형적으로 0 다음에 팔진 숫자가 오는 형식으로 지정해요.

int xInt = 077
assert xInt == 63

short xShort = 011
assert xShort == 9 as short

byte xByte = 032
assert xByte == 26 as byte

long xLong = 0246
assert xLong == 166l

BigInteger xBigInteger = 01111
assert xBigInteger == 585g

int xNegativeInt = -077
assert xNegativeInt == -63
십육진 리터럴 (Hexadecimal literal)

십육진수는 전형적으로 0x 다음에 십육진 숫자가 오는 형식으로 지정해요.

int xInt = 0x77
assert xInt == 119

short xShort = 0xaa
assert xShort == 170 as short

byte xByte = 0x3a
assert xByte == 58 as byte

long xLong = 0xffff
assert xLong == 65535l

BigInteger xBigInteger = 0xaaaa
assert xBigInteger == 43690g

Double xDouble = new Double('0x1.0p0')
assert xDouble == 1.0d

int xNegativeInt = -0x77
assert xNegativeInt == -119

5.2. 소수 리터럴 (Decimal literals)

소수 리터럴 타입은 자바와 같아요.

  • float
  • double
  • java.math.BigDecimal

다음 선언으로 이런 타입의 소수를 만들 수 있어요.

// primitive types
float  f = 1.234
double d = 2.345

// infinite precision
BigDecimal bd =  3.456

소수는 지수를 쓸 수 있어요. eE 지수 문자 다음에 선택적인 부호와 지수를 나타내는 정수가 와요.

assert 1e3  ==  1_000.0
assert 2E4  == 20_000.0
assert 3e+1 ==     30.0
assert 4E-2 ==      0.04
assert 5e-1 ==      0.5

정확한 소수 계산에 편리하게, Groovy는 소수 타입으로 java.math.BigDecimal을 선택해요. 게다가 floatdouble도 지원되지만, 명시적인 타입 선언, 타입 강제 변환 또는 접미사가 필요해요. 소수의 기본이 BigDecimal이어도, 그런 리터럴은 floatdouble을 파라미터 타입으로 받는 메서드나 클로저에서도 받아들여져요.

Note: 소수는 이진법, 팔진법, 십육진법으로는 표현할 수 없어요.

5.3. 리터럴 안의 밑줄 (Underscore in literals)

긴 리터럴 숫자를 쓸 때, 숫자들이 어떻게 묶이는지 인식하기 어려워요. 예를 들어 천 단위, 단어 단위로 묶인 숫자가 그렇죠. 숫자 리터럴에 밑줄을 넣을 수 있게 해 줌으로써 그런 묶음을 알아보기 쉬워요.

long creditCardNumber = 1234_5678_9012_3456L
long socialSecurityNumbers = 999_99_9999L
double monetaryAmount = 12_345_132.12
long hexBytes = 0xFF_EC_DE_5E
long hexWords = 0xFFEC_DE5E
long maxLong = 0x7fff_ffff_ffff_ffffL
long alsoMaxLong = 9_223_372_036_854_775_807L
long bytes = 0b11010010_01101001_10010100_10010010

5.4. 숫자 타입 접미사 (Number type suffixes)

접미사(아래 표 참고)를 주면 숫자(이진, 팔진, 십육진 포함)가 특정 타입을 가지도록 강제할 수 있어요. 대문자든 소문자든 쓸 수 있어요.

타입 접미사
BigInteger G 또는 g
Long L 또는 l
Integer I 또는 i
BigDecimal G 또는 g
Double D 또는 d
Float F 또는 f

예시를 볼게요.

assert 42I == Integer.valueOf('42')
assert 42i == Integer.valueOf('42') // lowercase i more readable
assert 123L == Long.valueOf("123") // uppercase L more readable
assert 2147483648 == Long.valueOf('2147483648') // Long type used, value too large for an Integer
assert 456G == new BigInteger('456')
assert 456g == new BigInteger('456')
assert 123.45 == new BigDecimal('123.45') // default BigDecimal type used
assert .321 == new BigDecimal('.321')
assert 1.200065D == Double.valueOf('1.200065')
assert 1.234F == Float.valueOf('1.234')
assert 1.23E23D == Double.valueOf('1.23E23')
assert 0b1111L.class == Long // binary
assert 0xFFi.class == Integer // hexadecimal
assert 034G.class == BigInteger // octal

5.5. 수학 연산 (Math operations)

연산자는 다른 곳에서 더 자세히 다루지만, 수학 연산의 동작과 그 결과 타입이 무엇인지 논의하는 건 중요해요. 나눗셈과 거듭제곱 이항 연산(아래에서 다룸)을 제외하고:

  • byte, char, short, int 사이의 이항 연산 결과는 int
  • long이 byte, char, short, int와 연관된 이항 연산 결과는 long
  • BigInteger와 다른 정수 타입이 연관된 이항 연산 결과는 BigInteger
  • BigDecimal이 byte, char, short, int, BigInteger와 연관된 이항 연산 결과는 BigDecimal
  • float, double, BigDecimal 사이의 이항 연산 결과는 double
  • BigDecimal과 BigDecimal 사이의 이항 연산 결과는 BigDecimal

다음 표가 그 규칙을 정리해 줘요.

byte char short int long BigInteger float double BigDecimal
byte int int int int long BigInteger double double BigDecimal
char int int int long BigInteger double double BigDecimal
short int int long BigInteger double double BigDecimal
int int long BigInteger double double BigDecimal
long long BigInteger double double BigDecimal
BigInteger BigInteger double double BigDecimal
float double double double
double double double
BigDecimal BigDecimal

Note: Groovy의 연산자 오버로딩 덕분에 평범한 산술 연산자가 BigInteger와 BigDecimal에서도 동작해요. 그 숫자들을 연산할 때 명시적인 메서드를 써야 하는 자바와는 달라요.

5.5.1. 나눗셈 연산자의 경우 (The case of the division operator)

나눗셈 연산자 /(나눗셈과 할당을 위한 /= 포함)는 피연산자 중 하나가 float나 double이면 double 결과를 만들고, 그 외의 경우(두 피연산자가 short, char, byte, int, long, BigInteger, BigDecimal 중 어떤 조합이든)에는 BigDecimal 결과를 만들어요.

BigDecimal 나눗셈은 나눗셈이 정확하면(즉 같은 정밀도와 스케일 경계 안에서 표현될 수 있는 결과가 나오면) divide() 메서드로 수행되고, 그렇지 않으면 두 피연산자의 정밀도 중 최댓값에 추가 정밀도 10을 더한 precision을 가진 MathContext와, 10과 피연산자들의 스케일 중 최댓값 중 큰 쪽을 사용해요.

Note: 자바에서처럼 정수 나눗셈을 하려면 intdiv() 메서드를 써야 해요. Groovy는 전용 정수 나눗셈 연산자 기호를 제공하지 않거든요.

5.5.2. 거듭제곱 연산자의 경우 (The case of the power operator)

거듭제곱 연산은 ** 연산자로 표현되고, 인자가 두 개예요. 밑수(base)와 지수(exponent)죠. 거듭제곱 연산의 결과는 피연산자와 연산 결과(특히 결과가 정수 값으로 표현될 수 있는지)에 따라 달라져요. Groovy의 거듭제곱 연산은 결과 타입을 결정할 때 다음 규칙을 사용해요.

  • 지수가 소수 값이면
    • 결과를 Integer로 표현할 수 있으면 Integer 반환
    • 그렇지 않고 결과를 Long으로 표현할 수 있으면 Long 반환
    • 그 외에는 Double 반환
  • 지수가 정수 값이면
    • 지수가 엄격히 음수면, 결과 값이 그 타입에 맞으면 Integer, Long, Double 반환
    • 지수가 양수거나 0이면
      • 밑수가 BigDecimal이면 BigDecimal 결과 값 반환
      • 밑수가 BigInteger면 BigInteger 결과 값 반환
      • 밑수가 Integer면 결과 값이 그 안에 맞으면 Integer, 그렇지 않으면 BigInteger 반환
      • 밑수가 Long이면 결과 값이 그 안에 맞으면 Long, 그렇지 않으면 BigInteger 반환

몇 가지 예시로 이 규칙을 설명할 수 있어요.

// base and exponent are ints and the result can be represented by an Integer
assert    2    **3    instanceof Integer    //  8
assert   10    **9    instanceof Integer    //  1_000_000_000

// the base is a long, so fit the result in a Long
// (although it could have fit in an Integer)
assert    5L   **2    instanceof Long       //  25

// the result can't be represented as an Integer or Long, so return a BigInteger
assert  100    **10    instanceof BigInteger //  10e20
assert 1234    **123    instanceof BigInteger //  170515806212727042875...

// the base is a BigDecimal and the exponent a negative int
// but the result can be represented as an Integer
assert    0.5  **-2    instanceof Integer    //  4

// the base is an int, and the exponent a negative float
// but again, the result can be represented as an Integer
assert    1    **-0.3f instanceof Integer    //  1

// the base is an int, and the exponent a negative int
// but the result will be calculated as a Double
// (both base and exponent are actually converted to doubles)
assert   10    **-1    instanceof Double     //  0.1

// the base is a BigDecimal, and the exponent is an int, so return a BigDecimal
assert    1.2  **10    instanceof BigDecimal //  6.1917364224

// the base is a float or double, and the exponent is an int
// but the result can only be represented as a Double value
assert    3.4f **5    instanceof Double     //  454.35430372146965
assert    5.6d **2    instanceof Double     //  31.359999999999996

// the exponent is a decimal value
// and the result can only be represented as a Double value
assert    7.8  **1.9  instanceof Double     //  49.542708423868476
assert    2    **0.1f instanceof Double     //  1.0717734636432956

6. Booleans (Booleans)

boolean은 참과 거짓, 즉 true와 false라는 진리 값을 나타내는 특별한 데이터 타입이에요. true/false 조건을 추적하는 단순한 플래그에 이 타입을 쓰세요. boolean 값은 다른 데이터 타입처럼 변수에 저장하고 필드에 할당할 수 있어요.

def myBooleanVariable = true
boolean untypedBooleanVar = false
booleanField = true

truefalse는 유일한 두 프리미티브 boolean 값이에요. 하지만 더 복잡한 boolean 표현식은 논리 연산자로 표현할 수 있어요. 게다가 Groovy에는 비- boolean 객체를 boolean 값으로 강제 변환하는 특별한 규칙(흔히 Groovy Truth라고 부름)이 있어요.

더 알아보기 (Learn more)