컬렉션
컬렉션 (Collections)
Groovy에서 컬렉션을 다루는 기본 문법을 볼게요. 리스트와 배열, 맵을 만드는 방법과 그 안의 값에 접근하는 방식을 자연스럽게 익힐 수 있을 거예요. 여러 언어에서 쓰던 표기법과 비슷하면서도, Groovy만의 편리한 표기 몇 가지가 눈에 띌 거예요.
본문
7. 리스트 (Lists)
Groovy는 쉼표로 구분하고 대괄호로 감싼 값들을 리스트로 표현해요. Groovy는 자체 컬렉션 클래스를 정의하지 않으므로, Groovy 리스트는 그냥 JDK의 java.util.List예요. 리스트 리터럴을 정의할 때 사용되는 구체적인 구현은 기본적으로 java.util.ArrayList인데, 나중에 보겠지만 달리 지정할 수도 있어요.
def numbers = [1, 2, 3] (1)
assert numbers instanceof List (2)
assert numbers.size() == 3 (3)
(1)쉼표로 구분하고 대괄호로 감싼 리스트numbers를 정의해서 변수에 할당해요.(2)이 리스트는 자바의java.util.List인터페이스의 인스턴스예요.(3)size()메서드로 리스트의 크기를 물어보면, 우리 리스트에 3개 요소가 있다는 걸 알 수 있어요.
위 예시에서는 같은 타입만 담은 동종(homogeneous) 리스트를 썼는데, 서로 다른 타입의 값들을 담은 이종(heterogeneous) 리스트도 만들 수 있어요.
def heterogeneous = [1, "a", true] (1)
(1)여기 리스트에는 숫자, 문자열, boolean 값이 들어 있어요.
앞서 리스트 리터럴은 기본적으로 java.util.ArrayList의 인스턴스라고 했는데, as 연산자로 타입을 강제 변환(coercion)하거나 변수 타입을 명시적으로 선언해서 리스트의 뒷받침 타입(backing type)을 다르게 쓸 수도 있어요.
def arrayList = [1, 2, 3]
assert arrayList instanceof java.util.ArrayList
def linkedList = [2, 3, 4] as LinkedList (1)
assert linkedList instanceof java.util.LinkedList
LinkedList otherLinked = [3, 4, 5] (2)
assert otherLinked instanceof java.util.LinkedList
(1)as연산자로 강제 변환해서 명시적으로java.util.LinkedList구현을 요청했어요.(2)리스트 리터럴을 담는 변수의 타입을java.util.LinkedList로 선언할 수도 있어요.
리스트 요소에는 [] 첨자(subscript) 연산자로 접근할 수 있어요. 값을 읽을 때도, 설정할 때도 같은 표기법을 쓰고, 양수 인덱스나 리스트 끝에서부터 세는 음수 인덱스, 그리고 범위(range)로도 접근할 수 있어요. << leftShift 연산자로는 리스트에 요소를 덧붙일 수 있어요.
def letters = ['a', 'b', 'c', 'd']
assert letters[0] == 'a' (1)
assert letters[1] == 'b'
assert letters[-1] == 'd' (2)
assert letters[-2] == 'c'
letters[2] = 'C' (3)
assert letters[2] == 'C'
letters << 'e' (4)
assert letters[ 4] == 'e'
assert letters[-1] == 'e'
assert letters[1, 3] == ['b', 'd'] (5)
assert letters[2..4] == ['C', 'd', 'e'] (6)
(1)리스트의 첫 번째 요소에 접근해요(0부터 세는 인덱스).(2)음수 인덱스로 마지막 요소에 접근해요.-1은 리스트 끝에서 첫 번째 요소예요.(3)할당을 사용해서 리스트의 세 번째 요소에 새 값을 설정해요.(4)<<leftShift 연산자로 리스트 끝에 요소를 덧붙여요.(5)두 요소를 한 번에 접근해서, 그 두 요소를 담은 새 리스트를 반환해요.(6)범위를 사용해서 리스트에서 시작 위치부터 끝 위치까지의 값 구간을 접근해요.
리스트는 본질적으로 이종일 수 있으니, 다른 리스트를 담아 다차원 리스트(make multidimensional lists)를 만들 수도 있어요.
def multi = [[0, 1], [2, 3]] (1)
assert multi[1][0] == 2 (2)
(1)숫자 리스트들로 이루어진 리스트를 정의해요.(2)가장 바깥 리스트의 두 번째 요소, 그 안쪽 리스트의 첫 번째 요소에 접근해요.
8. 배열 (Arrays)
Groovy는 배열에도 리스트 표기법을 재사용해요. 다만 그런 리터럴을 배열로 만들려면 강제 변환( coercion)이나 타입 선언으로 배열의 타입을 명시적으로 정의해야 해요.
String[] arrStr = ['Ananas', 'Banana', 'Kiwi'] (1)
assert arrStr instanceof String[] (2)
assert !(arrStr instanceof List)
def numArr = [1, 2, 3] as int[] (3)
assert numArr instanceof int[] (4)
assert numArr.size() == 3
(1)변수 타입을 명시적으로 선언해서 문자열 배열을 정의해요.(2)우리가 문자열 배열을 만들었는지 확인해요.(3)as연산자로 int 배열을 만들어요.(4)프리미티브 int 배열을 만들었는지 확인해요.
다차원 배열도 만들 수 있어요.
def matrix3 = new Integer[3][3] (1)
assert matrix3.size() == 3
Integer[][] matrix2 (2)
matrix2 = [[1, 2], [3, 4]]
assert matrix2 instanceof Integer[][]
(1)새 배열의 경계(bounds)를 정의할 수 있어요.(2)또는 경계를 지정하지 않고 배열을 선언할 수도 있어요.
배열 요소에 접근하는 방식은 리스트와 똑같아요.
String[] names = ['Cédric', 'Guillaume', 'Jochen', 'Paul']
assert names[0] == 'Cédric' (1)
names[2] = 'Blackdrag' (2)
assert names[2] == 'Blackdrag'
(1)배열의 첫 번째 요소를 가져와요.(2)배열의 세 번째 요소 값을 새 값으로 설정해요.
8.1. Java 스타일 배열 초기화 (Java-style array initialization)
Groovy는 항상 대괄호를 사용한 리스트/배열 리터럴 정의를 지원해 왔고, 클로저 정의와 충돌하지 않도록 Java 스타일의 중괄호는 피해 왔어요. 다만 중괄호가 배열 타입 선언 바로 뒤에 오는 경우에는 클로저 정의와 모호함이 없기 때문에, Groovy 3 이상에서는 Java 배열 초기화 표현식의 그 변형도 지원해요. 예시를 볼게요:
def primes = new int[] {2, 3, 5, 7, 11}
assert primes.size() == 5 && primes.sum() == 28
assert primes.class.name == '[I'
def pets = new String[] {'cat', 'dog'}
assert pets.size() == 2 && pets.sum() == 'catdog'
assert pets.class.name == '[Ljava.lang.String;'
// traditional Groovy alternative still supported
String[] groovyBooks = [ 'Groovy in Action', 'Making Java Groovy' ]
assert groovyBooks.every{ it.contains('Groovy') }
9. 맵 (Maps)
다른 언어에서는 딕셔너리나 연관 배열(associative arrays)이라고도 부르는 것을 Groovy는 맵으로 제공해요. 맵은 키를 값에 연결하고, 키와 값은 콜론으로 구분하며, 각 키/값 쌍은 쉼표로 구분하고, 전체를 대괄호로 감싸요.
def colors = [red: '#FF0000', green: '#00FF00', blue: '#0000FF'] (1)
assert colors['red'] == '#FF0000' (2)
assert colors.green == '#00FF00' (3)
colors['pink'] = '#FF00FF' (4)
colors.yellow = '#FFFF00' (5)
assert colors.pink == '#FF00FF'
assert colors['yellow'] == '#FFFF00'
assert colors instanceof java.util.LinkedHashMap
(1)문자열 색상 이름과 그에 대응하는 16진수 HTML 색상을 연결한 맵을 정의해요.(2)첨자 표기법으로red키에 연결된 내용을 확인해요.(3)프로퍼티 표기법으로green색상의 16진수 표현을 확인할 수도 있어요.(4)마찬가지로 첨자 표기법으로 새 키/값 쌍을 추가해요.(5)프로퍼티 표기법으로yellow색상을 추가해요.
Note: 키에 이름을 쓸 때는 실제로 맵에 문자열 키를 정의하는 셈이에요.
Note: Groovy는 실제로
java.util.LinkedHashMap의 인스턴스인 맵을 만들어요.
맵에 없는 키에 접근하려고 하면:
assert colors.unknown == null
def emptyMap = [:]
assert emptyMap.anyKey == null
null 결과를 얻는답니다.
위 예시에서는 문자열 키를 썼는데, 다른 타입의 값을 키로 쓸 수도 있어요.
def numbers = [1: 'one', 2: 'two']
assert numbers[1] == 'one'
여기서는 숫자를 키로 썼어요. 숫자는 모호함 없이 숫자로 인식되기 때문에, 앞선 예시처럼 문자열 키를 만들지 않아요.
그런데 변수를 키 자리에 넣어서, 그 변수의 값이 키가 되도록 하고 싶은 경우를 생각해 볼게요.
def key = 'name'
def person = [key: 'Guillaume'] (1)
assert !person.containsKey('name') (2)
assert person.containsKey('key') (3)
(1)'Guillaume'이름과 연결된 키는 실제로key변수의 값이 아니라"key"문자열이 돼요.(2)맵에'name'키는 없어요.(3)대신 맵에'key'키가 들어 있어요.
Note: 키로 따옴표로 감싼 문자열을 쓸 수도 있어요:
["name": "Guillaume"]. 이는 키 문자열이 유효한 식별자가 아닌 경우, 예를 들어["street-name": "Main street"]처럼 대시를 포함한 문자열 키를 만들고 싶을 때 필수예요.
맵 정의에서 변수의 값을 키로 전달해야 한다면, 그 변수나 표현식을 괄호로 감싸야 해요.
person = [(key): 'Guillaume'] (1)
assert person.containsKey('name') (2)
assert !person.containsKey('key') (3)
(1)이번에는 키 변수를 괄호로 감싸서, 문자열 키를 정의하는 게 아니라 변수를 전달하고 있다고 파서에 알려줘요.(2)이제 맵에name키가 들어 있어요.(3)하지만 이전처럼key키는 들어 있지 않아요.
더 알아보기 (Learn more)
- 문법 (Syntax) — 리스트, 배열, 맵 리터럴의 전체 규칙
- 연산자 (Operators) —
[],<<등 컬렉션 연산자 - Groovy 개발 키트 (GDK) — 컬렉션을 위한 풍부한 추가 메서드