Groovy가 Java와 다른 점

Groovy가 Java와 다른 점

GroovyJava의 차이에 대해 학습합니다

 

https://groovy-lang.org/differences.html


1. Default imports

아래의 패키지들은 그루비에서 기본적으로 포함되므로 명시적으로 import 할 필요가 없다.

  • java.io.*
  • java.lang.*
  • java.math.BigDecimal
  • java.math.BigInteger
  • java.net.*
  • java.util.*
  • groovy.lang.*
  • groovy.util.*

2. Multi-methods

그루비에서는 호출될 메서드가 런타임에 선택된다.

이게 무슨 말이냐면 파라미터 유형에 따라 매번 다른 메서드가 실행될 수 있다는 것이다.

이를 디스패치(Dispatch) 또는 다중 메서드(Multi-methods)라고 부른다.

자바는 정적 타이핑을 하기 때문에 선언된 유형에 따라 실행되는 차이가 있다.

int method(String arg) {
    return 1;
}
int method(Object arg) {
    return 2;
}
Object o = "Object";
int result = method(o);

// 자바
assertEquals(2, result);.

// 그루비
assertEquals(1, result);

메서드명이 같지만, 파라미터 유형이 다르다.

위는 String이고 아래는 Object이다.

자바는 위의 코드를 실행하면 타입 선언을 Object로 했기 때문에 실제 값이 String 이라도 Object로 선언된 메서드가 실행되어 2가 리턴되는데,

그루비는 타입선언이 Object로 됐지만, 실제 값은 String이기 때문에 String으로 선언된 메서드가 실행되어 1이 리턴된다.


3. Array initializers

자바에서 배열 초기화는 다음 두 가지 방식을 사용한다.

int[] array = {1, 2, 3};             // Java array initializer shorthand syntax
int[] array2 = new int[] {4, 5, 6};  // Java array initializer long syntax

하지만 그루비에서 대괄호({})는 클로저의 예약어이기 때문에 자바의 배열 리터럴을 사용할 수 없으며,

대신 다음과 같이 그루비만의 배열 리터럴을 사용한다. (자바스크립트에서 자주 보던 건데…?🤣)

int[] array = [1, 2, 3]

또한, 그루비 3.0+ 부터는 아래와 같은 문법도 사용 가능하다.

// Groovy 3.0+ supports the Java-style array initialization long syntax
def array2 = new int[] {1, 2, 3} 

4. Package scope visibility

자바는 접근 제한자를 따로 작성하지 않으면 자바 컴파일러가 접근 제한자를 default, 즉 package-private으로 설정해준다.

// In Java, package-private
class Person {
    String name
}

하지만 그루비는 접근 제한자를 생략하면 public이 돼버리기 때문에

package-private로 설정하고 싶다면 @PackageScope라는 어노테이션을 달아줘야 한다고 한다.

package model
class Person {
    @PackageScope String name = "홍길동"
}

package main
class Main {
    static void main(String[] args) {
        def person = new Person()
        println person.getName()
    }
}

package model
class Person {
    String name = "홍길동"
}

package main
class Main {
    static void main(String[] args) {
        def person = new Person()
        println person.getName()
    }
}

그루비는 더 간단한 문법을 지원하기 위해 JavaBeans와 유사한 GroovyBeans를 도입했다.

GroovyBeans 내의 프로퍼티들은 public 필드와 유사하므로 gettersetter를 명시적으로 정의할 필요가 없다.

근데 왜 필드에 직접 접근하는 건 허용되는지 아직은 잘 모르겠다.

package model
class Person {
    @PackageScope String name = "홍길동"
}

package main
class Main {
    static void main(String[] args) {
        def person = new Person()
        println person.name
    }
}


5. ARM blocks

이건 자바의 try-with-resource 문법을 말하는 것 같다.

Path file = Paths.get("/path/to/file");
Charset charset = Charset.forName("UTF-8");
try (BufferedReader reader = Files.newBufferedReader(file, charset)) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }

} catch (IOException e) {
    e.printStackTrace();
}

try절에 시스템 자원을 사용하는 코드를 파라미터로 던져주면

try블록이 끝나는 시점에 자원을 자동으로 반납한다. (단, AutoCloseable 인터페이스가 구현돼있어야 한다.)

그루비는 3.0+부터 이 문법을 지원하며, 또한 클로저를 이용한 문법이 더 효율적일 것이라고 한다.

new File('/path/to/file').eachLine('UTF-8') {
   println it
}

// 자바와 비슷한 문법을 원하는 경우
new File('/path/to/file').withReader('UTF-8') { reader ->
   reader.eachLine {
       println it
   }
}

뭔가 코드 추상화 수준이 자바랑 비교해서 말이 안 되는 것 같은데, 일단 그렇다고 하니 넘어간다.


6. Inner classes

익명 내부 클래스와 중첩 클래스의 구현은 자바의 문법과 매우 유사하지만, 몇 가지 차이점이 있다.
예를 들어 내부 클래스의 지역변수가 final일 필요는 없다. 왜냐하면 내부 클래스의 바이트 코드가 생성될 때 groovy.lang.Closure의 몇 가지 세부적인 구현 사항 위에 올려서 같이 보내기 때문이다.


6.1. Static inner classes

다음은 정적 내부 클래스의 예이다.

class A {
    static class B {}
}

new A.B()

그루비는 정적 내부 클래스의 지원이 가장 잘 되기 때문에,

내부 클래스를 이용해야 할 일이 생긴다면 가급적 정적 내부 클래스로 만들 것을 권장한다.


6.2. Anonymous Inner Classes

import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit

CountDownLatch called = new CountDownLatch(1)

Timer timer = new Timer()
timer.schedule(new TimerTask() {
    void run() {
        called.countDown()
    }
}, 0)

assert called.await(10, TimeUnit.SECONDS)

6.3. Creating Instances of Non-Static Inner Classes

자바에서는 다음과 같은 코드를 수행할 수 있다.

public class Y {
    public class X {}
    public X foo() {
        return new X();
    }
    public static X createX(Y y) {
        return y.new X();
    }
}

그루비는 3.0 이전 버전에서 y.new X()와 같은 문법을 지원하지 않기 때문에

아래 new X(y)와 같은 문법을 사용해야 한다.

public class Y {
    public class X {}
    public X foo() {
        return new X()
    }
    public static X createX(Y y) {
        return new X(y)
    }
}

하지만 그루비는 인수 없이 한 개의 파라미터로 메서드 호출을 지원하기 때문에, 이 경우 파라미터의 값은 null이 된다.

기본적으로 생성자도 메서드이기 때문에 이 규칙이 적용되며,

예를 들면 개발자가 new X(this) 대신 new X()를 작성할 위험이 있다.

다만 이게 일반적으로 사용될 수 있는 방법이기도 해서

아직 마땅한 해결책을 찾지 못했기 때문에, 유의해야 한다고 한다.

그리고 그루비는 3.0+부터 비 정적 내부 클래스의 인스턴스를 생성하기 위한 자바식 문법을 지원한다.


7. Lambda expressions and the method reference operator

자바 8부터 람다 표현식과 메서드 참조를 지원한다.

// Java
Runnable run = () -> System.out.println("Run");  
list.forEach(System.out::println);

그루비 3.0+부터 패럿 파서(Parrot parser)에서도 이를 지원한다.

그루비 3.0 이전 버전이라면 클로저를 이용해야 한다고 한다.

여기서 패럿 파서가 뭔지 아직 잘 모르겠다.

이것도 일단 이런 게 있구나 하고 넘어가자.

// Groovy 
Runnable run = { println 'run' }
list.each { println it } // or list.each(this.&println)

8. GStrings

그루비는 JDK의 java.lang.String 타입과 GDK의 groovy.lang.GString이라는 두 가지 타입을 갖는다.

홀따옴표로 선언한 문자열은 자바의 String 타입을 이용하며, 쌍따옴표로 선언한 문자열은 GString이 지원된다.

GString은 자바의 String에 비해 기능이 더 많은 듯하다.

def jString = 'Welcom to Groovy'
assert jString as java.lang.String

def language = "Groovy"
def gString = "Welcome to $language"
assert gString == "Welcome to Groovy"
assert gString as groovy.lang.GString

9. String and Character literals

그루비에서 홀따옴표로 작성된 리터럴은 String으로 사용되며,

쌍따옴표로 작성된 리터럴은 문자열에 보간이 있는지 여부에 따라 String혹은 GString으로 사용된다.

여기서 보간이라 함은 달러($) 표시를 의미하는 듯함.

assert 'c'.getClass()==String
assert "c".getClass()==String
assert "c${1}".getClass() in GString

그루비는 char타입 변수에 단일 문자 String을 할당할 때 char타입으로 자동형변환 한다.

그래서 char타입을 인자로 받는 메서드를 호출할 때는 타입 체크를 해야 한다.

char a='a'
assert Character.digit(a, 16)==10 : 'But Groovy does boxing'
assert Character.digit((char) 'a', 16)==10

try {
  assert Character.digit('a', 16)==10
  assert false: 'Need explicit cast'
} catch(MissingMethodException e) {
}

그루비는 두 가지 스타일의 형변환을 지원한다.

자바에서는 'cx'; 가 말도 안 되는 소리인데

그루비에서는 'cx';가 된다.

왜냐하면 자바에서 홀따옴표는 char타입을 의미하고, 그루비에서 홀따옴표는 자바의 String 타입을 의미하기 때문

그래서 아래와 같은 상황이 나오는 듯하다.

// for single char strings, both are the same
assert ((char) "c").class==Character // C-style
assert ("c" as char).class==Character // Groovy Style

// for multi char strings they are not
try {
  ((char) 'cx') == 'c'
  assert false: 'will fail - not castable'
} catch(GroovyCastException e) {
}
assert ('cx' as char) == 'c'
assert 'cx'.asType(char) == 'c'

C-Style은 아래 try 블록처럼 쓰면 예외를 뱉지만,

그루비 스타일은 관대하게 동작하여 예외를 뱉는 대신 첫 글자만 취해 char로 형변환 해준다.


10. Primitives and wrappers

그루비는 모든 것에 Object를 사용하기 때문에(동적 타이핑 때문인 듯),

기본 타입에 대한 참조에도 오토 박싱을 사용한다.

그래서 박싱보다 확장을 우선하는 자바와 다르게 동작한다.

다음은 그 예이다.

int i
m(i)

void m(long l) {           // java
  println "in m(long)"
}

void m(Integer i) {        // groovy
    println "in m(Integer)"
}

위 코드를 자바에서 작성하면 박싱보다 확장을 우선하기 때문에

intlong으로 확장되어 void m(long l) 메서드가 호출되지만,

그루비의 경우 박싱이 더 우선되므로 intInteger로 오토 박싱 되어 void m(Integer i) 메서드가 호출된다.


11. Behaviour of==

자바에서 ==는 인스턴스의 동일성을 의미한다.

하지만 그루비에서 ==는 자바의 equals()와 같이 동작하기 때문에

만약 자바의 ==를 그루비에서 사용하고 싶다면 is()===를 사용하면 된다.

class CompareTest {
    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
    }
}

12. Conversions

자바는 여러 가지 형변환을 지원한다.

  • Y : 묵시적 형변환 가능
  • C : 명시적 형변환 가능
  • T : 형변환이 가능하나 데이터의 손실이 발생하는 경우
  • N : 형변환 불가능

그루비는 다음과 같은 형변환을 지원한다.

  • Y : 묵시적 형변환 가능
  • D : 동적이거나 명시적 형변환 가능
  • T : 형변환이 가능하나 데이터의 손실이 발생하는 경우
  • B : 박싱, 언박싱 가능
  • N : 형변환 불가능

그루비는 형변환 중 발생하는 데이터의 손실을 판단하는 근거로 Groovy Truth를 이용한다.

Groovy Truth

그루비는 숫자를 문자로 변환할 때 Number.intValue()에서 char로 변환하며,

Float이나 Double에서 변환할 경우 Number.doubleValue()를 사용하여 자바의 BigIntegerBigDecimal을 생성한다.

이게 아니라면 toString()을 이용하여 생성한다.

다른 동작에 대해서는 java.lang.Number에 정의되어있다.


13. Extra keywords

그루비는 자바의 예약어 대부분을 예약어로 사용하며, 다음과 같은 예약어를 추가로 사용한다.

  • as
  • def
  • in
  • trait
  • it // 클로저의 파라미터를 지칭하는 기본 예약어

그루비는 자바처럼 엄격하지 않아 다음과 같은 코드가 유효하다.

// 이게 무슨 말도안되는 코드냐..... 이게 된다네요..
var var = [def: 1, as: 2, in: 3, trait: 4]

따라서 위와 같은 문법이 문법상 허용은 되지만 큰 혼동을 일으킬 수 있으므로 사용을 자제하라고 한다.


© 2022. All rights reserved.