티스토리 뷰

요약
JDK 1.5 Beta 2가 릴리즈 된지 몇 달이 지났다. 아직 Beta 버전이긴 하지만, JDK 1.5에서의 변화를 미리 살펴보기엔 부족함이 없다 생각된다. JDK 1.5에서 가장 먼저 눈에 띄는 것이 바로 문법적인 변화들이며, 본 글에서는 JDK 1.5에서 새로 추가/변경된 문법적 변화중에서 Generics, Autoboxing/unboxing, for 루프에 대해서 알아보도록 하겠다.


타입에 안전한 컬렉션 - Generics

JDK 1.4까지의 Collection 객체들은 모두 Collection에 담긴 객체를 얻을 때 Object 타입으로 리턴을 하기 때문에, 반드시 Collection으로부터 객체를 얻은 후 원하는 타입으로 캐스팅을 해야 했다. 따라서 Integer 객체만 넣어야 하는 Collection에 잘못해서 다른 타입의 객체가 존재할 때 Exception이 발생하게 되고, 이러한 상황을 방지하기 위해 Composition을 사용하거나, Collection 객체를 확장해서 타입 체크를 해줘야 했다.

JDK 1.5의 Generics는 타입이 정의된 Collection을 정의할 수 있도록 함으로써 이러한 수고로움을 언어적 측면에서 해결해준다. 어떻게 변경됐는 지 비교해보자. 먼저 JDK 1.4에서는 다음과 같은 방법으로 Collection을 사용했었다.

    // 기존의 Collection 사용법
    static void expurgate(Collection c) {
        for (Iterator i = c.iterator(); i.hasNext(); )
            if (((String)i.next()).length() == 4)    /* 캐스팅이 필요하다. */
                i.remove();
    }

JDK1.5에서는 다음과 같이 Generics를 사용한 코드를 사용할 수 있다.

    // 1.5에서의 Collection 사용법 - Generics
    static void expurgate(Collection<String> c) {
        for (Iterator<String> i = c.iterator(); i.hasNext(); )
            if (i.next().length() == 4) /* Collection에 타입이 정의되고, 캐스팅이 필요없다. */
                i.remove();
    }



Autoboxing / Unboxing

JDk 1.5에 추가된 사항으로 Auto Boxing과 Unboxing이 있다. 먼저 Boxing과 Unboxing에 대한 간단한 설명이 필요할 것 같다. 자바에는 short, int, long, float와 같은 프리미티브 타입이 있고, Short, Integer, Long 과 같이 각각의 프리미티브 타입에 대응하는 레퍼런스 타입을 제공하고 있다. 이들 타입들은 프리미티브 타입에 대한 일종의 래퍼 클래스(Wrapper Class)라고 할 수 있다.

Boxing은 이러한 래퍼 클래스를 사용해 프리미티브 타입의 값을 레퍼런스 타입으로 넣는 것을 말한다.반대로 Unboxing은 레퍼런스 타입의 값을 프리미티브 타입의 값으로 빼내는 것이다.

    int i = 1;
    Integer i1 = new Integer(i);   // boxing
    ...
    int j = i1.intValue();   // unboxing

위와 같은 작업은 레퍼런스 타입과 프리미티브 타입을 자주 변환해야 할 경우 매우 귀찮은 작업이 아닐 수 없었다.

그럼 Autoboxing/Unboxing은 무엇일까? 바로 Boxing과 Unboxing을 기존처럼 명시적으로 래핑하거나 메소드를 호출하지 않고, 바로 할당할 수 있는 것이 Autoboxing/Unboxing이다. 즉, 다음과 같은 코딩이 가능해지는 것이다.

    int i = new Integer(1); // Auto unboxing
    Integer i1 = 10; // Auto boxing

또한 다음과 같은 코딩도 가능하다.

    // 메소드에서의 Autoboxing/Unboxing
    method1( new Integer(1) ); // Auto unboxing
    method1( 1 );
    ...
    
    public void method1( int i ){
        System.out.println( "method1" );
    }

오토 Boxing과 Unboxing은 컬렉션에 프리미티브 타입을 넣을 때 그 빛을 발휘한다. 기존 1.4에서 컬렉션에 프리미티브 타입을 넣는 예제를 살펴보자.

    // 예시) 컬렉션에 프리미티브 타입 넣고 빼기
    int a = 0, b = 1, c = 2;
    
    List list = new ArrayList();
    list.add( new Integer(a) );
    list.add( new Integer(b) );
    list.add( new Integer(c) );
    
    int x;
    for( int i = 0; i < list.size(); i++ ){
        x = ( (Integer)list.get(i) ).intValue();
    }

기존의 코드에서 컬렉션에 프리미티브 타입을 넣고 빼는 과정을 설명해보면, 프리미티브 타입의 Boxing -> 리스트에 추가 -> 리스트에서 얻기 -> 캐스팅 -> Unboxing의 순서가 된다. 하지만 JDK 1.5에서는 리스트에 추가 -> 리스트에서 얻기로 끝난다. 단, 컬렉션 객체는 Generics로 인해, 컬렉션에 들어가는 객체 타입을 명시해 주어야만 한다.

    // 컬렉션에 프리미티브 타입 넣고 빼기
    int a = 0, b = 1, c = 2;
    
    List list = new ArrayList();
    list.add( a );
    list.add( b );
    list.add( c );
    
    int x;
    for( int i = 0; i < list.size(); i++ ){
        x = list.get(i);
    }



향상된 루프

프로그래밍을 하다보면 for 루프를 많이 사용하게 된다. for 루프의 문법은 모두 알다시피 for( ; ; )이다. 보통 for 루프를 돌릴 때 다음과 같이 코딩하게 된다.

    // for 루프
    int[] arr = { 0, 1, 2 };
    for( int i = 0; i < arr.length; i++ )
        System.out.println( arr[i] );

위의 예시를 보면 순환 변수인 i는 0부터 시작하고, 배열인 arr의 길이를 넘으면 안될 것이 뻔하다. 대부분의 for 루프도 마찬가지일 것이다. 순환 변수는 0부터 시작하고, 순차적으로 증가하며, 배열의 크기를 넘지 않는다. 이와 같은 특수한 경우를 위한 for 문이 존재하는데 이것이 for( : ) 이다. for ( : )를 사용하면 위 코드를 다음과 같이 작성할 수 있다.

    // JDK 1.5의 새롭게 추가된 for 루프 문법
    int[] arr = { 0, 1, 2 };
    for( int i : arr )
        System.out.println( arr[i] );
Static Import 

기존의 임포트 문은 클래스를 대상으로 하고 있다. 이러한 임포트 문을 정적 메소드와 필드로 확장한 것이 정적 임포트이다. 이 정적 임포트는 어떤 클래스에 정적 필드가 있고 다른 클래스에서 그 클래스의 필드만 참조하고 싶은 경우에 사용할 수 있다. 이 말은 기존의 임포트 문에서는 하나의 정적 필드를 참조하기 위해 해당 클래스를 임포트 해야 했지만, 정정 임포트를 사용하면 그렇게 하지 않아도 된다는 뜻이다. 또 정적 임포트를 사용하면 닷 표현(dot expression)을 사용하지 않고도 다른 클래스의 정적 멤버 및 메소드를 현재 클래스의 멤버 및 메소드 처럼 사용할 수 있게 된다.

다음의 예는 정적 임포트를 사용하는 예이다.

    import static java.lang.Math.*;
    
    class Test 
    {
        public static void main(String[] args) 
        {
            System.out.println( PI );
            System.out.println( cos(0.0) );
        }
    }

위의 예제를 보면 Math 클래스의 정적 멤버인 PI와 정적 메소드인 cos 메소드를 정적 임포트를 이용해 접근하는 것을 볼 수 있다. 물론 "import static java.lang.Math.PI;" 와 같이 특정 멤버만 임포트 할 수도 있다.



Typesafe Enums

Typesafe Enums를 굳이 번역하자면 "타입 안전 열거형" 정도가 되겠다. 타입 안전 열거형은 말 그대로 열거형 타입을 제공하는 것이다. 이전까지 자바에서는 열거방식을 주로 상수를 정의해서 구현했었다.

    public interface Num {
        public static final int ZERO = 0;
        public static final int ONE = 1;
        public static final int TWO = 2;
        ....
    }
    // Number Interface 사용
    ......
    int num;
    ...
    
    if( num == Num.ZERO ) {
        ...
    }

하지만 위의 방식은 타입에 안전하지 않은데, 타입에 안전하지 않다는 것은 다음과 같음을 말한다.

    public interface Language {
        public static final int JAVA = 0;
        public static final int CPP = 1;
        public static final int PERL = 2;
        ....
    }
    
    // Number Interface 사용
    ......
    int lan;
    ...
    if( lan == Num.ZERO ){  // equals mean, lan == Language.JAVA
        ...
    }

위 코드의 비교문에는 Num.ZERO가 아닌 Language.JAVA가 와야 의미상 맞지만, 프로그램적으로는 문제가 없으며, 컴파일시에도 역시 에러가 발생하지 않는다.

즉, 타입에 안전하지 않다는 것은 위와 같이 상수를 이용한 열거형에서는 상수 값만 같으면 사실상 열거 타입의 구분이 의미가 없어짐을 의미한다. 그렇다고 타입에 안전한 레퍼런스 타입을 활용하게 되면, switch ~ case 문에서 사용할 수 없다는 단점이 있다. 이런 문제를 해결하기 위해서 자바 1.5에서는 enum 타입을 제공하기 시작했다.

    class Test 
    {
        public enum Num { ZERO, ONE, TWO };
        
        public static void main(String[] args) 
        {
            Num num = Num.ONE;
            switch( num ){
                case ZERO:
                    System.out.println("0"); break;
                case ONE:
                    System.out.println("1"); break;
                case TWO:
                    System.out.println("2"); break;
                default:
                    break;
            }
        }
    }

열거 타입 그 자체가 타입이기 때문에, 열거 타입에 없는 값을 할당하게 되면 컴파일 에러가 발생하게 된다.



Varargs

Varargs를 굳이 번역하자면 "가변 인수" 정도 될 거 같다. 가변인수에 대해서 이해하기 위해서는 C의 printf 함수를 보면 된다. printf 함수의 인수는 개수가 정해져 있지 않다. printf 함수는 다음과 같이 인수의 개수를 다르게 사용할 수 있다.

    printf( "%d", 1 );
    printf( "%d %d", 1, 2 );
    printf( "%d %d %d", 1, 2, 3 );
    ......

하지만 자바에서는 가변 인수가 허용되지 않기 때문에 해당 메소드를 모두 구현해 주거나(오버로딩), 배열을 인수로 받아서 처리했었다. (정말 번거로운 일이 아닐 수 없다.) 자바 1.5에서는 이런 번거로움을 없애기 위해 C의 printf 함수와 같이 가변 인수를 갖는 메소드를 구현할 수 있도록 하였다.

    public void someMethod( Object... obj ){
        ....
    }

위의 예시와 같이 "..." 을 사용하여 가변인수를 갖는 메소드를 작성할 수 있다. 위의 예제 코드에서 Object... 로 선언된 obj는 Object[] 로 선언된 것과 동일하다고 할 수 있다. 따라서 가변 인수를 다음과 같이 처리할 수 있다.

    public void someMethod( Object... obj ){
        ....
        if( obj.length > 0 ){
            ....
        }
    }

가변인수는 오토방식/언박싱과 함께 사용되어 다음과 같은 상황에서도 컴파일 에러가 나지 않는다.

    someMethod( new Integer(1) );
    someMethod( 1 );
    ......
    public void someMethod( Integer... obj ){ // equals "int... obj"
        ....
        if( obj.length > 0 ){
            ....
        }
    }

1.5 버전으로 소스 코드 컴파일하기

베타 버전은 아무 옵션없이 컴파일 하면 기본적으로 1.4를 기준으로 컴파일하기 때문에, 1.5를 기준으로 코딩된 소스를 컴파일 하면 컴파일 에러가 뜨는 경우가 발생한다. 이 때에는 컴파일 옵션을 "-source 1.5" 로 주고 컴파일 하면 문제없이 컴파일된다. 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG more
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함