사용자 도구

사이트 도구


study:java:javachobo:appendix

문서의 이전 판입니다!


JDK 1.5에 추가된 기능

오토박싱(autoboxing)

컬렉션에는 객체로 저장해야하기 때문에 기본형 값을 저장하기 위해서는 Integer나 Long과 같은 Wrapper클래스를 사용해야했다. 그러나 이제부터는 기본형 값을 직접 컬렉션에 저장할 수 있다. 컴파일러에 의해서 자동적으로 Wrapper클래스로 변환되어 저장되는

향상된 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);
        }
    }
}
study/java/javachobo/appendix.1269000711.txt.gz · 마지막으로 수정됨: 2010/03/19 21:11 저자 gauryan