본문 바로가기

카테고리 없음

자바 프로그래밍 언어에서의 제너릭(Java Generics)

자바 프로그래밍 언어에서의 제너릭(Java Generics)

제너릭(Generics) 
JDK 1.5에는 자바 언어를 여러 가지로 확장했는데, 그 중에 하나가 제너릭(Generics)이다. 제너릭은 다양한 타입을 가지는 콜렉션 타입을 컴파일러에게 알려주는 수단을 제공해서, 컴파일러가 정확한 타입을 검사할 수 있도록 한 자바 언어 확장이다. C++에서의 템플릿(Template)과 유사하지만, 중요한 차이점이 있다.

먼저 간단한 예제를 한번 살펴보자.
// Removes 4-letter words from c. Elements must be string static void expurgate(Collection c) {     for (Iterator i = c.iterator(); i.hasNext(); )        if (((String) i.next()).length() == 4)            i.remove(); }
위 예제를 보면, 콜렉션이 보관하는 객체는 무조건 String이라는 가정을 하고 있다. 프로그래머도 이를 가정하고 Iterator i를 통해 가져온 Object 객체를 String 객체로 타입캐스팅을 하여 문자열의 길이를 검사하고 있다. 그러나 파라미터로 전달된 콜렉션이 String이 아닌 Integer를 보관하고 있다면 어떻게 될까? 그래서 콜렉션의 Interator i가 Interger 객체를 리턴한다면 어떻게 될까? 첫번째 예제에서는 exception이 발생할 것이다. 이 경우 프로그래머는 콜렉션이 담고 있는 데이타 객체들의 타입을 알기 위해서 expurgate()의 두번째 파라미터로 콜렉션의 데이타 타입을 전달받고자 할 것이다. 뭔가 불편하지 않은가? 다음에 제너릭을 이용해 위 예제를 수정해 보자.
// Removes the 4-letter words from c static void expurgate(Collection< String > c) {     for (Iterator i = c.iterator(); i.hasNext();)         if (i.next().length() == 4)             i.remove(); }
제너릭을 이용한 코드에서 "< type >"을 읽을 때는, "type의 무엇"이라 읽는다. 즉, 위 코드의 경우 Collection c는 "String 타입의 콜렉션"이라 읽는다. 수정 예제는 제너릭을 이용해 콜렉션이 보관하고 있는 타입이 String이라고 명시한 경우이다. 이 경우 프로그래머는 컬렉션에서 Object를 가져온 후 이것이 String 타입의 객체라 확신할 수 있고, 코드 상으로 타입캐스팅을 하지 않고도 문자열 길이를 검사할 수가 있다. 이로서 명쾌해 졌다. 와일드카드(Wildcard) 다음 예제를 살펴보자.
void printCollection(Collection< Object > c) {    for (Object e : c) {       System.out.println(e);    } }
위 예제는 Object 타입의 객체를 담고 있는 Collection c를 파라미터로 받아 Object 타입의 객체를 출력하는 예제이다. 이 예제의 의도는 Object 타입이 모든 객체의 베이스 타입(클래스)이므로 일반화된 출력 루틴을 작성하려고 하는 것이다. 그러나 위 예제는 컴파일러가 printCollection()의 파라미터로 Collection< Object > 타입만을 허용할 뿐, 실제로는 임의 타입의 객체를 담는 Collection을 허용하지 않는다. 즉, 아래와 같은 경우 오류가 발생한다.
Collection< String > cs; ... printfCollection(cs); // error
콜렉션 cs는 Collection< String > 이지, Collection< Object > 타입이 아니기 때문이다. 그렇다면, 임의 타입의 컬렉션은 어떻게 표현할까? 이 경우 와일드카드("?")로 명시한다. "Collection"는 "언논 타입의 콜렉션"(Collection of unknown)으로 부르며, 결정되지 않은 즉 모르는 타입의 객체를 담고 있는 콜렉션을 의미한다.  와일드카드를 이용해 위 예제를 다시 고쳐 쓰면 아래와 같다.
void printCollection(Collection< ? > c) {    for (Object e : c) {       System.out.println(e);    } }
명확해 졌는가? 이제 바운디드 와일드카드(Bounded Wildcards)에 대해 살펴보자. Shape 베이스클래스가 있고, 여기에서 상속받은 Circle과 Rectangle이라는 클래스가 정의되어 있다고 하자. Canvas에 그려져야 할 각각의 Circle과 Rectangle 객체들이 리스트에 담겨져 있고, Canvas에 drawAll()을 구현하고자 할 때 아래와 같이 선언한다.
public void drawAll(List< ? extends Shape > shapes) { ... }
와일드카드와 함께 사용된 extends를 잘 보기 바란다. 이를 바운디드 와일드카드라 한다. 이렇게 선언하면, drawAll()은 Shape에서 상속받은 언논 타입을 담고 있는 List를 파라미터로 받는다는 의미를 가진다. 즉, drawAll()은 Shape, Circle, Rectangle 객체의 List만을 파라미터로 받게 된다. 그 이외에는 모두 컴파일 오류가 난다. 위 경우처럼 바운디드 와일드카드를 사용하면, 단순히 와일드카드를 사용하는 것보다는 타입의 범위를 좀 더 명확히 할 수 있게 된다. 그러나 주의할 점은 shapes가 정확히 어떤 타입의 객체를 담고 있는지에 대해서는 알 수가 없다는 것이다. 그래서는 안되지만, 만약 drawAll()에서 shapes.add(0, new Rectangle()); 을 넣는다고 할 때, 컴파일러는 오류를 내뱉는다. 언논(unknown)은 모른다는 뜻임을 상기하기 바란다. 자 이제 이 문제를 해결할 수 있는 제너릭 메소드를 살펴보자. 제너릭 메소드(Generic Methods) 아래 두개의 예제 코드를 먼저 보자.
static void fromArrayToCollection(    Object[] a, Collection< ? > c) {       for (Object o : a) {          c.add(o); // comple time error       } } static < T > void fromArrayToCollection(     T[] a, Collection< T > c) {       for (T o : a) {          c.add(o); // correct    } }
위 두 예제의 차이를 알 수 있는가? 첫번째 예제는 콜렉션 c에 Object 배열인 a를 더하려는 코드이다. 그러나 콜렉션 c는 c가 담을 수 있는 타입이 언논이기 때문에, Object 타입의 o를 파라미터로 넘길 수가 없다. 반면, 두번째 예제는 제너릭 메소드라는 방법을 사용해서 첫번째 예제에서 발생한 문제를 해결한 경우이다. 그렇다면, 언제 제너릭 메소드를 사용하고, 언제  와일드카드를 사용해야 할까? 와일드카드는 타입 파라미터가 폴리모피즘을 지원하고자 할 때 사용한다. 반면, 제너릭 메소드는 타입 파라미터가 다른 파라미터의 타입이나 리턴 타입 사이의 종속성을 명시해야 할 때 사용한다. 자세한 내용은 참고에서 "Generics in the Java Programming Language"를 참고하기 바란다. 참고
  1. Generics in the Java Programming Language
  2. Generics

http://qyleekr.blogspot.kr/2010/05/java-generics.html