사용자 도구

사이트 도구


study:java:javachobo:appendix

문서의 이전 판입니다!


JDK 1.5에 추가된 기능

오토박싱(autoboxing)

컬렉션에는 객체로 저장해야하기 때문에 기본형 값을 저장하기 위해서는 Integer나 Long과 같은 Wrapper클래스를 사용해야했다. 그러나 이제부터는 기본형 값을 직접 컬렉션에 저장할 수 있다. 컴파일러에 의해서 자동적으로 Wrapper클래스로 변환되어 저장되는 데 이것을 오토박싱(autoboxing)이라고 한다. 뿐만아니라 저장된 값을 꺼낼 때도 변환과정을 거치지 않고도 기본형 형태의 값을 바로 얻을 수 있는 데 이것을 언박싱(unboxing)이라고 한다.

ArrayList list = new ArrayList();
list.add(new Integer(10));
list.add(new Integer(20));
list.add(new Integer(30));
 
Integer i = (Integer)list.get(0);
int value = i.intValue();

이전에는 위와 같은 코드를 사용했지만 이제는 아래와 같이 코드를 간략히 할 수 있다.

ArrayList<Integer> list = new ArrayList<Integer>();
list.add(10); // 오토박싱(autoboxing)
list.add(20); // 오토박싱(autoboxing)
list.add(30); // 오토박싱(autoboxing)
 
int value = list.get(0); // 언박싱(unboxing)

향상된 for 문

배열과 컬렉션에 저장된 요소에 접근할 때 기존보다 편리한 방법으로 처리할 수 있도록 for문의 새로운 문법이 추가되었다.

1. 배열
    for( 배열의 타입 변수명 : 배열 ) {
        // 반복할 문장
    }
 
2. 컬렉션
    for( 컬렉션에 저장된 요소의 타입 변수명 : 컬렉션 ) {
        // 반복할 문장
    }

먼저, 정수형 배열이 다음과 같이 선언되어 있다고 가정하자.

int[] arr = {10, 20, 30, 40, 50};

아래의 두 for 문은 서로 동일하다.

for(int i=0 ; i < arr.length ; i++) {
    System.out.println(arr[i]);
}
for(int i : arr) {
    // arr[i]가 아닌 i라는 것에 유의
    System.out.println(i);
}

이번엔 ArrayList를 생성하고 다음과 같이 세 개의 Integer객체를 저장하고 각 요소에 접근하기 위해 Iterator를 얻었다.

ArrayList<Integer> list = new ArrayList<Ingeger>();
list.add(new Ingerger(10));
list.add(new Ingerger(20));
list.add(new Ingerger(30));
 
Iterator it = list.iterator();
for(;it.hasNext();) {
    System.out.println(it.next());
}
for(Integer i : list) {
    System.out.println(i);
}

향상된 for 문은 배열보다는 컬렉션에 저장된 요소에 접근할 때 더욱 코드가 간결해진다는 것을 알 수 있다. 한 가지 주의해야할 점은 향상된 for 문내에서 삭제와 같이 컬렉션을 변경하는 동작을 해서는 안 된다는 것이다.

제네릭스(Generics)

제네릭스는 컬렉션 프레임워크와 관련된 기능으로 컬렉션에 저장하는 객체의 타입을 컴파일시에 체크하기 때문에 객체의 타입 안정성을 높이고 꺼낼 때는 자동으로 형변환해주기 때문에 편리하다.

컬렉션에 저장할 객체의 타입을 저장하기 위해 아래와 같이 한다. 이렇게 하면 컬렉션에 저장할 수 있는 객체는 지정한 타입의 객체뿐이다.

컬렉션클래스<저장할 객체의 타입> = new 컬렉션클래스<저장할 객체의 타입>;

아래의 코드는 ArrayList에 Tank객체만 저장할 수 있도록 작성한 것이다. Tank객체 이외의 객체를 저장하려고 하면 컴파일 시에 에러가 발생한다.

// Tank클래스의 객체만을 저장할 수 있는 ArrayList를 생성
ArrayList<Tank> list = new ArrayList<Tank>();
list.add(new Tank());
list.add(new Dropship()); // 컴파일 에러 발생 !!!

저장된 객체를 꺼낼 때는 형변환하지 않아도 자동형변환이 된다. 제네릭스를 적용한 코드와 그렇지 않은 코드를 잘 비교해보도록 하자.

ArrayList list = new ArrayList();
list.add(new Tank());
Tank t = (Tank)list.get(0);
ArrayList<Tank> list = new ArrayList<Tank>();
list.add(new Tank());
Tank t = list.get(0);

만일 다형성을 사용해야 하는 경우에는 조상타입을 지정함으로써 여러 종류의 객체를 저장할 수 있다.

class Unit{}
class Tank extends Unit {}
class Dropship extends Unit {}
 
// Unit클래스의 자손객체들을 저장할 수 있는 ArrayList를 생성
ArrayList<Unit> list = new ArrayList<Unit>();
list.add(new Tank());     // 컴파일 에러가 발생하지 않는다.
list.add(new Dropship()); // 컴파일 에러가 발생하지 않는다.
 
Unit u = list.get(0);       // 형변환이 필요없다.
Tank t = (Tank)list.get(0); // 형변환을 필요로 한다.

ArrayList가 Unit의 객체를 저장하도록 지정하면, 이들의 자손인 Tank와 Dropship의 객체도 저장할 수 있다. 다만 꺼내올 때 원래의 타입으로 형변환해야 한다.

다음은 ArrayList의 실제 소스의 일부이다.

public class ArrayList<E> extends AbstractList<E>
       implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private transient E[] elementData;
    public boolean add(E e) { /* 내용생략 */ }
    public E get(int index) { /* 내용생략 */ }
    ...
}

위의 코드에 사용된 'E'는 객체의 타입을 의미한다. 'Element'의 약자로 지정한 것인데 어떤 문자를 사용해도 상관없지만 소스코드 내에서 같은 문자를 일관되게 사용해야 한다. 이는 마치 메서드의 매개변수이름을 다르게 해도 메서드 내에서만 같은 이름을 사용하면 문제없는 것과 같다. 이를 이해하기 어렵다면 'E'를 Object타입으로 이해해도 좋다. 예를 들어 ArrayList를 ArrayList<Tank> list = new ArrayList<Tank>();와 같이 생성하는 경우는 실제소스코드가 아래와 같이 작성되어 있는 셈이다. 'E'가 'Tank'로 치환된 것과 같다고 보면 된다.

public class ArrayList<Tank> extends AbstractList<Tank>
       implements List<Tank>, RandomAccess, Cloneable, java.io.Serializable
{
    private transient Tank[] elementData;
    public boolean add(Tank e) { /* 내용생략 */ }
    public Tank get(int index) { /* 내용생략 */ }
    ...
}
public static void printAll(ArrayList<Unit> list) {
    for(Unit u : list) {
        System.out.println(u);
    }
}

위와 같이 선언된 메서드가 있을 때, 이 메서드의 매개변수로는 Array<Unit>타입의 변수만 사용할 수 있다. 즉, ArrayList<Unit> list = new ArrayList<Unit>();으로 생성된 객체만 매개변수로 사용될 수 있다는 뜻이다.

public static void printAll(ArrayList<? extends Unit> list) {
    for(Unit u : list) {
        System.out.println(u);
    }
}

하지만 위와 같이 'T'라는 타입이 있고 그 타입이 Unit의 자손이라고 선언하면 이 메서드의 매게변수는 Unit의 자손인 Tank과 Dropship을 저장하는 ArrayList<Tank>타입 또는 ArrayList<Dropship>타입의 참조변수를 지정할 수 있다.

만일 아래와 같은 같은 코드가 있다면 타입을 별도로 선언함으로써 코드를 간략히 할 수 있다. 아래의 두 코드는 동등한 의미의 코드이므로 잘 비교해보자.

public static void printAll(ArrayList<? extends Unit> list, ArrayList<? extends Unit> list2) {
    for(Unit u : list) {
        System.out.println(u);
    }
}
public static <T extends Unit> void printAll(ArrayList<T> list, ArrayList<T> list2) {
    for(Unit u : list) {
        System.out.println(u);
    }
}

[주의] 여기서 만일 Unit이 클래스가 아닌 인터페이스라 할지라도 키워드로 'implements'를 사용하지 않고 클래스와 동일하게 'extends'를 사용한다는 것에 주의하자.

예제 : /GenericsEx1.java

import java.util.*;
 
class Unit() {}
class Tank extends Unit {}
class Dropship extends Unit {}
 
class GenericsEx1 {
    public static void main(String[] args) {
        ArrayList<Unit> unitList = new ArrayList<Unit>();
        ArrayList<Tank> tankList = new ArrayList<Tank>();
 
        unitList.add(new Tank());
        unitList.add(new Dropship());
 
        tankList.add(new Tank());
        tankList.add(new Tank());
 
        printAll(unitList);
//      printAll(tankList);  // 컴파일 에러가 발생한다.
 
        printAll2(unitList);
        printAll2(tankList);
    }
 
    public static void printAll(ArrayList<Unit> list) {
        for( Unit u : list ) {
            System.out.println(u);
        }
    }
 
//  public static void printAll2(ArrayList<? extends Unit> list) {
    public static <T extends Unit> void printAll2(ArrayList<T> list) {
        for( Unit u : list ) {
            System.out.println(u);
        }
    }
}

타입에 안전한 열거형 (Typesafe enums)

이전까지 자바는 C언어와 달리 열거형이라는 것이 존재하지 않았으나 새로 추가되었다. 자바의 열거형은 C언어의 열거형보다 더 향상된 개념의 열거형으로 열거형이 갖는 값 뿐만아니라 타입까지 관리하기 때문에 보다 논리적인 오류를 줄일 수 있다.

class Card {
    static final int CLOVER  = 0;
    static final int HEART   = 1;
    static final int DIAMOND = 2;
    static final int SPADE   = 3;
 
    static final int TWO     = 0;
    static final int THREE   = 1;
    static final int FOUR    = 2;
 
    final int kind;
    final int num;
}
class Card {
    enum Kind { CLOVER, HEART, DIAMOND, SPADE }
    enum Value { TWO, THREE, FOUR }
 
    final Kind kind;    // 타입이 int가 아닌 Kind임에 유의하자.
    final Value value;
}

이전 방식으로는 타입이 달라도 값이 같으면 조건식결과가 true였으나, 새로 도입된 타입에 안전한 열거형에서는 실제 값이 같아도 타입이 다르면 조건식의 결과가 false가 된다. 이처럼 값뿐만 아니라 타입까지 체크하기 때문에 안전한(typesafe) 열거형이라고 하는 것이다.

if(Card.CLOVER == Card.TWO)            // true지만 false이어야 의미상 맞음.
if(Card.Kind.CLOVER == Card.Value.TWO) // false

예제 : EnumEx.java

import java.util.*;
 
class EnumEx {
    public static void main(String[] args) {
        Deck deck = new Deck();
 
        System.out.println(deck.pick(0));
        System.out.println(deck.pick(1));
        System.out.println(deck.pick(2));
    }
}
 
class Card {
    enum Kind { CLOVER, HEART, DIAMOND, SPADE }
    enum Value { TWO, THREE, FOUR, FIVE, SIX,
                 SEVEN, EIGHT, NINE, TEN,
                 JACK, QUEEN, KING, ACE }
 
    final Kind kind;
    final Value value;
 
    Card(Kind kind, Value value) {
        this.kind = kind;
        this.value = value;
    }
 
    Card() {
        this(Kind.SPADE, Value.ACE);
    }
 
    public String toString() {
        return "["+kind+","+value="]";
    }
}
 
class Deck {
    ArrayList<Card> cards = new ArrayList<Card>();
 
    Deck() {
        for(Card.Kind kind : Card.Kind.values()) {
            for(Card.Value value : Card.Value.values()) {
                cards.add(new Card(kind, value));
            }
        }
    }
 
    Card pick(int index) {
        return cards.get(index);
    }
}
study/java/javachobo/appendix.1269001818.txt.gz · 마지막으로 수정됨: 2010/03/19 21:30 저자 gauryan