Groovy 핵심 문법

Groovy 핵심 문법

Groovy의 디테일한 핵심 문법에 대해 알아봅니다

 

Groovy 핵심 문법

그루비는 확실히 자바 엔지니어에게는 매우 쉬운 언어가 맞는 듯하다.

그루비에 대해 공부하고 느낀 것은 그냥 자바라는 그림 위에 점을 하나 찍고 “저는 그루비에요~” 라고 하는 것과 같다.

그래서 생각보다 다룰 내용이 얼마 없다.

기본적인 그루비의 문법은 자바와 거의 같고, 자바의 모든 라이브러리도 사용할 수 있다.

그러면서 동적 타이핑을 지원하기 때문에 자바에 비해 너그럽다.

다만 이는 단점이 될 수도 있다. 컴파일 단계에서 코드의 에러를 잡아준다는 것은 굉장히 좋은 일이기 때문이다.

마지막으로 자바의 대부분의 규칙과, 예약어를 공유하며 추가적인 예약어가 더 있다.

아래의 표를 참고하시면 되겠다.

 

 

자바 엔지니어라면 그루비를 공부하면서 고려해야 할 부분은 몇 개 없다.

왜냐하면 진짜 모르겠으면 그냥 자바처럼 써도 어지간하면 다 돌아가기 때문이다.

  • 동적 타이핑으로 인해 특정 부분에서 예상과 다르게 동작할 수 있다
  • 연산자 오버로딩이 가능하다
  • 클로저를 사용할 수 있다

동적 타이핑으로 인한 문제는 내가 동적 타이핑을 좋아하지 않기 때문에 그루비로 코딩을 해도 정적 타이핑으로 작성해서 별 의미가 없었고, 연산자 오버로딩은 굉장히 쓸만한 기능이라고 느껴졌다.

마지막으로 클로저가 그루비의 핵심인 듯하다.

클로저는 변수에 정의한 함수 조각? 작은 함수? 라고 생각하면 될 것 같다.

이는 함수형 프로그래밍에서 사용되는 개념으로 자바의 메서드를 일급시민으로 취급하여 메서드 자체를 파라미터로 넘기는 것과 비슷한 효과를 누린다.

어떻게 보면 자바의 람다식과 크게 다를 게 없는것도 같다.


1. 세미콜론을 생략할 수 있다

// 세미콜론을 생략할 수 있다(파이썬을 생각하면 된다)
// 하지만 여러줄의 코드를 한줄에 작성 할 경우엔 생략할 수 없다
StringBuilder stringBuilder = new StringBuilder()
String a; String b

2. 소괄호를 생략할 수 있다

// 소괄호를 생략할 수 있다
// 하지만 추천하지 않는 방법으로 명시적으로 선언해주는게 좋다고 보는 분위기인 듯
// if문의 구문이 한줄일 경우 중괄호를 생략하는 것과 비슷하다고 생각하면 될 듯
print 'a'
println 'hello'.substring(0, 1)
testMethod "hello"

3. System.out을 생략할 수 있다

// System.out 은 기본적으로 import 돼있으므로 생략 가능
System.out.println('') // java
println('') // groovy

System.out.printf('') // java
printf('') // groovy

System.out.print('') // java
print('') // groovy

4. GString

// GString은 GDK에 추가된 여러 기능을 더 사용 할 수 있다
// 대표적으로 보간기능이 있다 -> "hi, i\'m $param"
// 쌍따옴표는 GDK GroovyGString
// 홀따옴표는 JDK String
String gdk = "hello"
String jdk = 'hello'

5. package-private

/**
 * @PackageScope : 접근제한 package-private으로 설정. 생략하면 public (자바는 default임)
 */
class GroovyAccessModifier {
    @PackageScope String name = "홍길동"
}

6. Getter와 Setter 자동 작성(코틀린과 똑같다)

// Getter와 Setter는 그루비가 자동으로 작성해준다
static class GroovyBeans {
    String name;
    int age;

    GroovyBeans(String name, int age) {
        this.name = name
        this.age = age
    }

    // return은 생략해도 되며, return type을 def로 선언하면 동적타이핑이 된다
    // 이 코드블록의 경우 쌍따옴표를 사용했으므로 GString을 반환 할 것이며, 보간사용 가능
    def getInformation() {
        "name: $this.name | age: $this.age"
    }
}
    
    
def beans = new GroovyBeans("groovy", 10)
println(beans.getInformation()) // name: groovy | age: 10

7. return 생략 가능

Bucket plus(Bucket other) {
    new Bucket(this.size + other.size) // return 생략 됨
}

8. 연산자 오버로딩

https://groovy-lang.org/operators.html#Operator-Overloading

package groovy

class GroovyOperatorOverloading {
    static void main(String[] args) {
        def a = new Bucket(1)
        def b = new Bucket(2)

        println((a + b).size) // 3
        println((a - b).size) // -1
        println((a / b).size) // 0.5
        println((a * b).size) // 2
        println((a % b).size) // 1
    }
}

class Bucket {
    def size

    Bucket(def size) {
        this.size = size
    }

    // Operator Overloading
    // https://groovy-lang.org/operators.html#Operator-Overloading
    Bucket plus(Bucket other) {
        new Bucket(this.size + other.size)
    }

    Bucket minus(Bucket other) {
        new Bucket(this.size - other.size)
    }

    Bucket div(Bucket other) {
        new Bucket(this.size / other.size)
    }

    Bucket multiply(Bucket other) {
        new Bucket(this.size * other.size)
    }

    Bucket mod(Bucket other) {
        new Bucket(this.size % other.size)
    }
}


9. 동일성, 동등성 검사

/**
 * 자바의 ==는 동일성비교, equlas()는 동등성 비교
 * 그루비는 ===혹은 is()가 동일성 비교, ==가 동등성 비교
 */
class GroovyCompare {
    static void main(String[] args) {
        String s1 = new String("안녕하세요") // 생성자를 사용했으므로 서로 다른 인스턴스
        String s2 = new String("안녕하세요")
        println(s1==s2) // true
        println(s1===s2) // false
        println(s1.is(s2)) // false
    }
}

10. Assertions

코드의 유효성을 검증하는 데 사용한다.

그루비의 assertions는 매우 강력하다.

class GroovyAssertions {
    static void main(String[] args) {
        // GroovyAssertions
        int i = 1
        assert (i == 1) // int i = 1 이므로 true
        assert ['a'] // List가 비어있지 않으므로 true
        assert ['a': 1] // Map이 비어있지 않으므로 true
        assert 'a' // String이 비어있지 않으므로 true
    }
}


11. Collections

class GroovyCollections {

    // 리터럴 표기법 사용 시 중괄호({})는 Groovy에서 클로저의 예약어이므로 대괄호([])사용
    static void main(String[] args) {
        //-------------------------------------- List --------------------------------------//
        List<Integer> l1 = [1, 2, 3, 4] // Java Collection 사용 정적 타이핑
        def l2 = [1, 2, 3, 4] // def 예약어를 사용한 동적 타이핑 + 리터럴 표기법
        def l3 = ['Hi', 1, true, File] // 여러 타입의 요소를 같은 List에 포함시킬수도 있다

        def list = []  // 빈 List 선언
        list += [1, 2, 3] // List에 요소 추가
        assert list == [1, 2, 3] && list.size == 3 // 제대로 추가됐는지 검증

        list << 4 << 5 // List에 다음과 같은 방법으로도 요소를 추가할 수 있다 (C와 유사하다)
        assert list == [1, 2, 3, 4, 5]

        list.add(6) // List에 다음과 같은 방법으로도 요소를 추가할 수 있다 (Java의 방식)
        assert list == [1, 2, 3, 4, 5, 6]

        assert list[0] == 1 // Java 원시 배열: 인덱스를 직접 입력하여 요소를 꺼내는 방법
        assert list.get(0) == 1 // Java Collection: 인덱스를 직접 입력하여 요소를 꺼내는 방법
        assert list.getAt(1) == 2 // Groovy Collection: 인덱스를 직접 입력하여 요소를 꺼내는 방법
        assert list[-1] == 6 // 인덱스를 음수로 줄 경우 역순으로 검색한다. 이 경우 인덱스는 0이 아닌 1부터 시작
        assert list[-3] == 4 // 인덱스를 음수로 줄 경우 역순으로 검색한다. 이 경우 인덱스는 0이 아닌 1부터 시작

        list.putAt(1, 1) // 1번 인덱스(2)에 1을 덮어씌움
        assert list == [1, 1, 3, 4, 5, 6]
        assert list.set(1, 2) == 1 // 1번 인덱스(1)에 2를 덮어씌움. set메서드는 덮어씌워져 제거된 값을 리턴한다
        assert list == [1, 2, 3, 4, 5, 6]

        // Groovy는 stream 사용 시 별다른 이름을 지정하지 않으면 기본적으로 it을 사용한다
        list.each {
            // 모든 원소를 순차적으로 출력
            println "$it"
        }
        list.eachWithIndex {
                // 모든 원소와 인덱스를 출력
            it, index -> println "item: $it, index: $index"
        }

        list -= 1 // 원소를 제거. 일치하는 원소가 여러개 있을 경우 모두 제거된다
        assert list == [2, 3, 4, 5, 6]
        list = list.minus([2, 3, 4]) // 여러 원소를 제거
        assert list == [5, 6]

        //-------------------------------------- Map --------------------------------------//
        def map = ['name': 'shirohoo', 'hobby': 'develop'] // Map 선언
        assert map.size() == 2 // Map 사이즈 검증

        map += ['skills': ['java', 'groovy']] // Map에 List를 원소로 추가
        assert map == ['name': 'shirohoo', 'hobby': 'develop', 'skills': ['java', 'groovy']]

        map['age'] = 28 // 배열에 key:age로 새로운 값 추가
        assert map == ['name': 'shirohoo', 'hobby': 'develop', 'skills': ['java', 'groovy'], 'age': 28]

        // 값을 확인하는 여러가지 방법들
        assert map.name == 'shirohoo'
        assert map['name'] == 'shirohoo'
        assert map.get('name') == 'shirohoo'
        assert map.getAt('name') == 'shirohoo'
        assert map.skills[0] == 'java'

        map.each {
                // 모든 원소를 순차적으로 출력
            it -> println it.key + ":" + it.value
        }

        map.eachWithIndex {
                // 모든 원소와 인덱스를 출력
            it, index -> println "item $index - " + it.key + ":" + it.value
        }

        //-------------------------------------- range --------------------------------------//
        def range = 1..10 // 1~10까지 순차적으로 선언
        assert range == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
        range = 'a'..'c' // a~b까지 선언
        assert range == ['a', 'b', 'c']

        range = 1..<8 // 1~7까지 선언
        assert range == [1, 2, 3, 4, 5, 6, 7]

        (1..5).each {
            // 1~5까지 선언, 출력
            println it
        }

        assert [*3..10] == [3, 4, 5, 6, 7, 8, 9, 10] // *이 앞에 추가되면 실제로 구현함
        assert [5, 7, *2..4] == [5, 7, 2, 3, 4] // List [5,7]에 2~4까지를 순차적으로 추가함
    }
}

12. Closures

http://www.groovy-lang.org/closures.html

클로저는 함수에 함수를 넘기는 용도로 사용된다.

자바 메서드의 상위호환 버전이라고 봐도 될 것 같다.

함수에 넘겨진 클로저는 부모 함수의 상태를 가지고 동작할 수 있다.

대부분의 함수형 언어에 있는 기능이기도 하다.

class GroovyClosure {

    // 클로저 정의
    def closure = { key, value ->
        {
            println(key)
            println(value)
            ["$key" : "$value"]
        }
    }
}

class Main {
    static void main(String[] args) {
        def groovyClosure = new GroovyClosure()

        // 호출 방법 1: 클로저변수만 사용
        println('방법 1')
        println(groovyClosure.closure('groovy', 'closure'))

        // 호출 방법 2: 클로저변수.call
        println('방법 2')
        println(groovyClosure.closure.call('groovy', 'closure'))
    }
}


© 2022. All rights reserved.