사용자 도구

사이트 도구


study:java:javachobo:appendix

문서의 이전 판입니다!


JDK 1.5에 추가된 기능

제네릭스(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> 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() {}
study/java/javachobo/appendix.1268999529.txt.gz · 마지막으로 수정됨: 2010/03/19 20:52 저자 gauryan