03. 그루비의 자료형

본 강좌는 그루비 공식사이트 (http://groovy.codehaus.org/Korean+Home) 와 인사이트 출판사(http://www.insightbook.co.kr)의 프로로그래밍 그루비(http://www.yes24.com/24/goods/3301480?scode=032&OzSrank=1) 책의 설명을 참조하였음을 알려드립니다.


그루비의 변수 타입



그루비는 변수 타입을 선언시점에 미리 지정하지 않아도 되는 동적타이핑 언어입니다.
자바의 경우 타입을 지정해야 하고, primitive type 자료형(int, short, byte, char, long ... 등) 이 있지만, 그루비는 primitive type 이 없고 모두 객체로 간주하는 특성이 있습니다. 따라서 내부적으로는 Wrapper Type으로 인식하게 됩니다. (int 인 경우 Integer로 처리 됩니다.)

int x = 10; // java
x = 10      // groovy 


세미콜론(;) 은 붙이든, 붙이지 않든 상관없습니다. 다만, 문장의 종료를 구분해야 한다면 코드 가독성상으로 구분 짓는것이 좋겠습니다.

또한 메서드의 return 키워드가 생략가능하고 메서드 호출을 위한 소괄호도 생략 가능 합니다.

접근 제한자는 public, protected, private 으로 나눠지고 접근 제한자를 생략한 경우에는 public 으로 간주하게 됩니다.

그루비 콘솔을 열고 아래와 같이 코딩한 후 출력 결과를 확인해 보도록 하겠습니다.
def myList = [5,"str", "4.0", 4.0, 2.0f, 2.0d]
    myList.each{ println it.class.name }


결과
java.lang.Integer
java.lang.String
java.lang.String
java.math.BigDecimal
java.lang.Float
java.lang.Double
Result: [5, str, 4.0, 4.0, 2.0, 2.0]


def를 쓴경우 변수형은 할당한 값에 따라 변하게 되는데 List Type에 지정된 값을 출력해보면 각각의 래퍼타입으로 처리된 것을 알 수 있습니다.

x = 1
println x
println x.class.name

x = new java.util.Date()
println x
println x.class.name

x = -3.1499392
println x
println x.class.name

x = false
println x
println x.class.name

x = "Hi"
println x
println x.class.name


결과

1
java.lang.Integer
Fri Apr 05 16:03:00 KST 2013
java.util.Date
-3.1499392
java.math.BigDecimal
false
java.lang.Boolean
Hi
java.lang.String


이처럼 사용되는 시점에 타입이 할당된다는 것을 알 수 있습니다.
또한 Wrapper Type 클래스의 참조형 객체로 Autoboxing 을 포함한 데이터 객체로 프로그래밍을 지원한다는것을 알 수 있습니다.

Groovy의 List


대표적인 자료형인 List 와 Map을 살펴보도록 합니다.
이전까지는 GroovyConsole로 코딩을 해왔지만 지금의 예제부터는 GGTS(Groovy/Grails Tool Suite)를 통해 작성해보도록 하겠습니다.

  1. GGTS의 소개 사이트 : GGTS소개
  2. GGTS의 다운로드 : GGTS다운로드
GGTS는 이클립스 기반의 STS(Spring Source Tool Suite)와 기본적으로 동일하다고 보시면 됩니다. 거기에 그루비 & 그레일즈를 지원하기 위한 플러그인이 추가된 형태로 배포된다고 생각하시면 접근하기 편할것 입니다.

먼저 아래의 예제를 통해 List 객체의 선언과 사용에 대해서 살펴보겠습니다.
package org.gliderwiki.project.datatype

class ListTest {

	public static void main(args){
                // List 선언 
		List<Integer> list = [1,2,3,4]
		println list[0]
		println list[1]
		println list[2]
		List<Person> persons = list[]
		Person p = new Person("GLiDER", "Wiki")
		persons[0] = p
		println persons.size()
		println persons[0].firstName
		println persons.get(0).firstName
		println persons.get(0).lastName
	}
}


Groovyconsole로 실행할 경우 main 메서드 없이 List 선언부 부터 작성하신 후 실행하시면 됩니다(실행하는 법은 이전 강좌에 설명했습니다)

일단 결과부터 보도록 하겠습니다.



저는 맥북이라 실행 단축키가 약간 다릅니다만, Groovy 파일 오른 클릭 후 Run as < Java Application 을 실행하면 됩니다.

결과는 아래와 같습니다.
ListTest.groovy 실행 결과
1
2
3
1
GLiDER
GLiDER
Wiki


List 객체인 list를 선언 한 후 각 요소들을 출력합니다. 요소의 시작점은 자바의 배열처럼 0번째부터 시작됩니다.
또한 persons 라고 하는 Person클래스의 List를 초기화 한 상태에서 생성하였습니다.

Person 클래스는 성과 이름의 프러퍼티를 갖는 간단한 빈입니다.

class Person {

	String firstName;
	String lastName;
	Person(String firstName, String lastName){
	  this.firstName = firstName
	  this.lastName= lastName
	}
}


persons 리스트에 Person객체를 할당 한 후 각 요소에 접근하는 방법을 확인 할 수 있습니다.
Groovy 에서 List구문은 자바와는 다르게 GDK에 추가된 더 많은 메서드를 사용할 수 있습니다.

또한, pop() 메서드를 통해 List를 스택처럼 사용할 수 있으며 같은 Collection 구조인 Set처럼도 사용할 수 있습니다.

Set 은 데이터의 중복을 허용하지 않으며, 순서가 없습니다.


아래의 예제를 확인해 보겠습니다.

package org.gliderwiki.project.datatype

class CreateSet {

	static void main(args) {
		// Create a list
		def colorList = ["Red" , "Green" , "Blue", "Red" , "Green" , "Blue"]
		println "colorList : "  + colorList
		 
		// Create a set
		def colorSet = ["Red" , "Green" , "Blue", "Red" , "Green" , "Blue"] as Set
		println "colorSet : "  + colorSet
		
		// List change set 
		def listSet = colorList as Set
		println "listSet : "  + listSet
		
		println listSet.class.name
    }
}



실행 결과를 보면 as Set으로 선언한 자료형은 중복값이 제거된 Set 데이터타입이 된것을 확인 할 수 있습니다.

실행결과
colorList : [Red, Green, Blue, Red, Green, Blue]
colorSet : [Red, Green, Blue]
listSet : [Red, Green, Blue]
java.util.LinkedHashSet


마지막으로 리스트의 내용에 접근하는 예제를 살펴보도록 하겠습니다.

package org.gliderwiki.project.datatype

class ListRemoveAdd {

	public static void main(args) {
		def list = [1,2,3]
		println list.count(2) // 요소 2가 있는 갯수 = 1 
		println list.max()    // 요소의 최대값 = 3
		println list.min()	  // 요소의 최소값 = 1
		
		while (list) {
			list.remove(0)	// 요소삭제 
		}
		
		println list  // [] 
		println list.size()+1 // 1  
				
		while(list.size() < 3) {
			println("list.size() : " + list.size())
			list << list.size() + 1  // shift 연산 			
		}
		
		println list // [1,2,3]
		
		def even = list.find{ item ->
			item % 2 == 0			 
		}
		
		println even  // 2 
		
		println list.every { item -> item < 5 }  // true 
		println list.any { item -> item < 2 }    // true 
		
		def store = ''
		
		list.each { item ->
			store += item
		}
		
		println store  // 123 
		
		store = ''
		
		list.reverseEach{ item ->
			store += item
		} 
		
		println store  // 321
		println list.sum()  // 6
		println list.join('-')  // 1-2-3 
	}	
}


중간에 되풀이 (반복문) 구문이나 클로저의 코딩방벙이 나오는데, 이는 추후 다시 한번 살펴보기로 하고 각 메서드들의 실행 결과를 살펴보면 어렵지 않게 어떤 기능을 수행하는지 알수 있을것입니다.
다음음 맵의 사용법에 대해서 살펴보도록 하겠습니다.


Groovy 의 Map



맵은 Key:Value 쌍으로 이루어진 비교적 자주 쓰는 자료형입니다. 또한 Key는 중복이 허용되지 않습니다.
아래의 예제를 통해 맵의 초기화와 요소를 호출하는 법에 대해서 알아보겠습니다.

package org.gliderwiki.project.datatype

class MapTest {
	public static void main(args){
		Map map = [:]
		def map2 = ["홍":"길동", "갑":"돌이"]
		println map2["홍"]
		map2["Test"] = "Tester"
		println map2["Test"]
		println map2 		
	}
}


만약 Key가 중복된 값이 있다면 뒤에 지정한 Key가 앞의 값을 엎어치게 됩니다. 또한 보통의 경우 Key에 문자열을 사용하기 때문에작은 따옴표나 큰 따옴표는 생략이 가능합니다.

Map의 정의는 [:] 와 같이 아무런 값이 없는 초기화와 def ITService = [SDS: '삼성 SDS', CNS : 'LG CNS', CNC : 'SK C&C'] 와 같이 선언과 함께 초기화 하는 방식으로 정의 할 수 있습니다.

맵은 일반적인 JDK에서 제공하는 메서드(isEmpty(), size(), containsKey(), containsVlaue()) 이외에 GDK에서 추가한 any 나 every, subMap, find 같은 메서드도 사용이 가능합니다.

아래는 맵을 활용하여 지정된 단어의 출현빈도를 분석해서 통계를 내보는 프로그램입니다..

package org.gliderwiki.project

def textCorpus =
"""
Look for the bare necessities
The simple bare necessities
Forget about your worries and your strife
I mean the bare necessities
Old Mother Nature's recipes
That bring the bare necessities of life
"""
	
def words  = textCorpus.tokenize()
println words //  tokenizer through the space 
def wordFrequency = [:]
words.each { word ->
	wordFrequency[word] = wordFrequency.get(word, 0) + 1
}

def wordList  = wordFrequency.keySet().toList()
wordList.sort { wordFrequency[it] }

def statistic = "n"
wordList[-1..-6].each { word ->
	statistic += word.padLeft(12) + ': '
	statistic += wordFrequency[word] + "n"
}

println statistic




결과를 살펴보면 아래와 같습니다.
결과
necessities: 4
bare: 4
the: 3
your: 2
life: 1
of: 1


Groovy의 String



String을 정리하기 전에 assert 를 살펴보겠습니다.


- assert는 기대되는 값을 비교식을 통해 판단하는 Unit Test용 구문입니다.
- 의사코드나 로직에 대한 점검을 코드의 영향 없이 검증할 수 있습니다. 단, 기대값이 다를 경우 Exception을 발생시킵니다.
- assertArrayEquals, assertContains, shouldFail 등 좀 더 다양한 Assertion을 통해 값의 흐름, 코드의 리턴값을 검증할 수 있습니다.


일반적으로 아래 코드 처럼 사용할 수 있습니다.
assert 1 == 1
assert 2 + 2 == 4 : "We're in trouble, arithmetic is broken"


Groovy의 문자열은 Java와 같이 java.lang.String 클래스를 이용하지만 간편한 사용을 위해 몇가지 기능과 연산자가 추가 되었다.

- 싱글 부호 ('String') , 더블 부호("String") 를 다 쓸수있다.
- 문자열 내의 append 없이 여러줄의 문자열을 정의할 수 있다. (""" 여러줄의 문자열 """)
- 동적 변수 할당자인 GString을 통해 문자열을 변수처럼 쓸수있다.


아래의 소스를 실행해 보겠습니다.
package org.gliderwiki.project.datatype

class StringType {

	static void main(args) {
		assert 'hello, world' == "hello, world"
		assert "Hello, Groovy's world" == 'Hello, Groovy's world'
		assert 'Say "Hello" to the world' == "Say "Hello" to the world"
		
		def name = 'Groovy'
		assert "hello $name, how are you today?" == "hello Groovy, how are you today?"
		
		def o = new Object()
		assert String.valueOf( o ) == o.toString() //this works for any object in Groovy
		println "o is : ${o.toString()}"
		println "String o is ${String.valueOf(o)}"
		
		def s= 'abcdefg'
		assert s.length() == 7 && s.size() == 7
		assert s.substring(2,5) == 'cde' && s.substring(2) == 'cdefg'
		assert s.subSequence(2,5) == 'cde'
		
		assert 'hello'.padRight(8,'+').padLeft(10,'+') == '++hello+++'
		assert 'hello'.padLeft(7).padRight(10) == '  hello   '
		assert 'hello'.center(10, '+').center(14, ' ') == '  ++hello+++  '
		
		assert 'hello'.reverse() == 'olleh'
		assert 'hello'.count('l') == 2
		assert 'abcdefg'.getAt( [ 1, *3..5 ] ) == 'bdef'
		assert 'abcdefg'.getAt( [ 1, 3..5 ] ) == 'bdef'
		
		assert 1 == 1
		assert 2 + 2 == 3 : "We're in trouble, arithmetic is broken"
	}
}


실행을 해보면 16~17라인에 객체의 두 주소값은 동일함을 알 수 있습니다.
또한 마지막 구문에서 assert의 기대값과 다른 비교식으로 인하여 아래와 같은 익셉션이 발생함을 알 수 있습니다.



String 객체를 활용한 메서드 역시 JDK 를 포함하여 GDK에 확장된 메서드까지 좀 더 다양하게 활용이 가능합니다.

그루비에서도 자바 처럼 자료형을 명시 할 수 있수 있습니다.
그러나, def 키워드를 통해 자료형을 미리 지정하지 않고 동적 할당을 수행 할 수 있습니다.
물론 자바에서 제공하는 자료형 또한 동일하게 사용할 수 있습니다.

int c = 1 이라고 선언하더라도 실행시점에는 Integer 타입으로 래핑한 객체타입이 할당 됩니다.
결론적으로 def를 통하여 암시적 자료형을 선언할 수도, int 나 float 처럼 자바에서 쓰는 primitive type의 자료형도 명시적으로 선언하여 쓸 수 있고, Integer 나 String 과 같이 참조형으로 명시할 수도 있습니다.

정적 자료형과 동적 자료형을 선택하는 것은 그루비의 장점이자 개발하는 입장에서 시스템에 안전하게 쓰일 수 있기 때문에 코드의 간결성에 유리하게 작용할 수 있고, 굳이 선언형으로 해도 자바가 허용하는 한도에서는 수행이 됩니다.