Groovy가 Java와 다른 점
Groovy
와 Java
의 차이에 대해 학습합니다
- 1. Default imports
- 2. Multi-methods
- 3. Array initializers
- 4. Package scope visibility
- 5. ARM blocks
- 6. Inner classes
- 7. Lambda expressions and the method reference operator
- 8. GStrings
- 9. String and Character literals
- 10. Primitives and wrappers
- 11. Behaviour of==
- 12. Conversions
- 13. Extra keywords
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 필드와 유사하므로 getter
와 setter
를 명시적으로 정의할 필요가 없다.
근데 왜 필드에 직접 접근하는 건 허용되는지 아직은 잘 모르겠다.
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)"
}
위 코드를 자바에서 작성하면 박싱보다 확장을 우선하기 때문에
int
가 long
으로 확장되어 void m(long l)
메서드가 호출되지만,
그루비의 경우 박싱이 더 우선되므로 int
가 Integer
로 오토 박싱 되어 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
를 이용한다.
그루비는 숫자를 문자로 변환할 때 Number.intValue()
에서 char
로 변환하며,
Float
이나 Double
에서 변환할 경우 Number.doubleValue()
를 사용하여 자바의 BigInteger
나 BigDecimal
을 생성한다.
이게 아니라면 toString()
을 이용하여 생성한다.
다른 동작에 대해서는 java.lang.Number
에 정의되어있다.
13. Extra keywords
그루비는 자바의 예약어 대부분을 예약어로 사용하며, 다음과 같은 예약어를 추가로 사용한다.
- as
- def
- in
- trait
- it // 클로저의 파라미터를 지칭하는 기본 예약어
그루비는 자바처럼 엄격하지 않아 다음과 같은 코드가 유효하다.
// 이게 무슨 말도안되는 코드냐..... 이게 된다네요..
var var = [def: 1, as: 2, in: 3, trait: 4]
따라서 위와 같은 문법이 문법상 허용은 되지만 큰 혼동을 일으킬 수 있으므로 사용을 자제하라고 한다.