바에 제네릭스(generics)를 도입하기 위한 연구는 이미 8년 전인 1996년부터라고 한다. 실제로 자바에 제네릭스를 도입하 는 몇 가지 방안들이 논문으로 나오기 시작한 것이 1998년 초임을 감 안하면 무려 8년이 지난 후에야 자바 5.0에 전격 채택되었다는 것은 이것이 얼마나 어려운 일이었나 하는 것을 보여준다. 자바의 스펙을 결정하는 표준화 절차인 JSR을 보면 제네릭스를 논 의하는 JSR 14가 형성된 것은 1999년이었다. 하지만 아쉽게도 자바 1.4가 나온 시점이었던 2002년까지 제네릭스를 자바에 적용할 수 없 었다. 제네릭스를 어떻게 구현할 것인가에서부터 제네릭스를 도입한 영향을 어떻게 최소화하고 기존 자바 버전과의 역방향 호환성을 유지 할 수 있을 것인가 하는 이슈에 이르기까지 제네릭스는 그야말로 뜨 거운 감자였던 것이다. 조금 뒤늦은 감이 있긴 하지만 자바 5.0이 출시된 지금도 여전히 다른 방식의 제네릭스 구현을 선호하는 그룹들이 있으며, Thinking In Java 로 유명한 브루스 에켈은 서운한 감정을 자신의 블로그에서 여과 없이 드러내고 있기도 하다. 물론 추후 자바 버전에서 다른 방식의 제네릭스 구현이 채택되지 않는다는 보장은 없을 것이다. 특히 C#이라는 만만찮은 경쟁 상대를 두게 된 현실에서는 더욱 더 그러하다. C#에서도 최근 제네릭스가 채 택되었으며 그 구현 방식은 자바와 반대이며 C++와 유사하다는 점 에서 자바와 C#의 제네릭스 성공 여부가 주목받을 것 같다. 제네릭스 사용하기 자바 제네릭스 클래스들은 java.util 패키지의 컬렉션 라이브러리 클 래스들과 밀접한 관련을 가지고 있다. 어떻게 보면 컬렉션 라이브러 리를 사용할 때 자료형 안정성을 좀 더 보장할 방법이 없을까 하는 용 도로 만든 것이 자바 제네릭스가 아닐까 생각될 정도이다. 자바 5.0의 컬렉션 라이브러리 클래스들은 모두 제네릭스를 사용 하도록 다시 정의되었다. 따라서 컬렉션 라이브러리를 사용할 때 제 네릭스는 분명한 효용성을 보인다. <리스트 1>은 간단하지만 자바 5.0을 처음 접하는 자바 프로그래머 라면 몇 가지 어색한 점을 발견했을 것이다. 제네릭스 외에 사용된 자 바 언어의 2가지 새로운 기능은 오토박싱/언박싱(autobox/unbox), foreach 스타일의 for 반복문이다. 202
ilist.add(new Integer(1))과 같이 사용하지 않고 ilist.add(1)과 같이 사용한 것은 자바 5.0에 추가된 기본 자료형과 해당 객체 자료 형과의 오토박싱/언박싱 기능을 사용한 것으로 오토박싱이란 자바 컴파일러가 객체를 요구하는 곳에 기본 자료형이 대입될 경우 자동으 로 해당하는 기본 자료형의 랩퍼 객체(wrapper object)로 변환해주 는 것을 말하며 오토언박싱은 그 반대의 일을 뜻한다. foreach 스타일의 for 반복문은 컬렉션 클래스에서 Iterator를 사 용하는 번잡함을 자바 컴파일러가 대신 수행해주는 개념으로 for 반 복문 조건식 괄호가 변수와 : 부호 그리고 컬렉션 객체로 선언될 경 우 지정된 컬렉션 객체의 Iterator를 순차하면서 그 내용이 되는 객 체를 변수에 매번 대입해주는 개념이다. 영어로는 foreach ~ in 이 라고 읽는다. 자, 이제 꺽쇠 괄호를 포함하는 리스트 선언들을 보자. 이 부분에서 바로 제네릭스가 사용되었다. UseCollection 소스코드에서 ArrayList 클래스와 List 인터페이스는 더 이상 모든 객체를 수용하 는 리스트가 아니다. slist 변수는 String 객체만 받아들이며, ilist 객 체는 Integer 객체만 받아들인다. 즉, 다음과 같이 사용한다면 자바 컴파일러가 컴파일 에러를 발생시킨다. 제네릭스를 사용하는 가장 적합하고 중요한 목적은 바로 컬렉션 클 래스들에 사용될 객체들의 자료형을 엄밀하게 제한하는 것이다. 제네릭스 만들기 지금까지는 아주 순조롭게 자바의 새로운 기능 제네릭스를 느껴볼 수 있었다. 자, 이제 제네릭스 클래스를 한 번 만들어보자. 제네릭스 클 래스를 만들기 전에 경고를 하나 해야겠다. 혹시 C++ 템플릿을 정 의해본 적이 있다면, 그 경험으로 제네릭스 클래스도 쉽게 정의할 수 있다고 생각하면 큰 오산이다. 헬로, 제네릭스 클래스는 조금 불편 public class MyVector<E> { public static final int ARRAY_SIZE = 10; private E[] elements; private int elementcount = 0; public MyVector() { elements = (E[]) new Object[ARRAY_SIZE]; // unchecked typecast warning slist.add(3); ilist.add( world! ); public void add(e value) { if (elementcount >= ARRAY_SIZE) { throw new IndexOutOfBoundsException( element count reached max size ); import java.util.*; elements[elementcount++] = value; public class UseCollection { List<String> slist = new ArrayList<String>(); // String의 리스트 slist.add( hello ); slist.add( generics ); for (Object o : slist) { System.out.println( string value = + o); public E get(int index) { if (index >= elementcount) { throw new ArrayIndexOutOfBoundsException(index); return elements[index]; MyVector<Integer> vector = new MyVector<Integer>(); List<Integer> ilist = new ArrayList<Integer>(); // Integer의 리스트 ilist.add(1); ilist.add(2); for (int i = 0; i < 10; i++) { vector.add(i); for (Object o : ilist) { System.out.println( integer value = + o); for (int i = 0; i < 10; i++) { System.out.println( [ + i + ]th value : + vector.get(i)); 203
한 경험이 될 것이다. 먼저 MyVector 클래스 선언을 살펴보자(<리스 트 2>). 최대한 간단하게 구현하기 위해 크기가 항상 10으로 고정된 벡터 클래스를 상정하였다. 일단 클래스 선언에서 제네릭 자료형인 E를 선언하고 있다. 그리고 멤버 필드로 선언된 elements의 자료형이 역 시 제네릭 자료형인 E, add 메쏘드의 인자도, get 메쏘드의 반환 유 형도 모두 E로 선언되어 깔끔한 듯이 보인다. C++ 개발자였다면 여기까지 당연하게 받아들이면서 자바 언어 는 역시 조금이라도 더 간단한 구문으로 제네릭스를 지원하려니 하고 술술 넘어갔을 수도 있다. 그런데 가만히 보면 생성자 내용이 조금 이 상하다. elements = (E[]) new Object[ARRAY_SIZE]; Object 배열 자료형에서 제네릭 자료형인 E의 배열 자료형으로 명 시적인 형 변환이 일어났다. 자바 제네릭스의 비밀을 모른다면 당연 히 다음과 같이 시도했을 것이다. elements = new E[ARRAY_SIZE]; 자바 컴파일러는 무심하게도 이 코드를 generic array creation 이라는 에러로 처리한다. 자바의 제네릭 자료형은 객체를 생성할 수 없다!!! 즉, 제네릭 자료형 이름이 T라면 new T()를 할 수 없다. 즉, 선언 은 할 수 있지만 객체 인스턴스를 만들 수 없는 유령 자료형이라는 것 이다. 자바 소스코드의 제네릭 자료형이 컴파일시까지만 존재하고 실 제 컴파일된 바이트코드에는 존재하지 않기 때문에 실행 시간에 해당 하는 제네릭 자료형의 인스턴스를 만드는 것은 원천적으로 불가능하 다. 마찬가지 이유로 제네릭 자료형의 배열도 생성할 수가 없다. 생성 자에서 컴파일러 에러가 난 이유는 제네릭 자료형의 배열을 생성하려 고 시도했기 때문이다. 불쾌함은 약간 더 지속되는데 MyVector.java 소스코드를 컴파일 해 보면 생성자 부근에서 컴파일러 경고가 나타난다. 컴파일러 경고 내용을 보려면 -Xlint:unchecked 옵션을 사용해야 한다. javac -Xlint:unchecked MyVector.java MyVector.java:29: warning: [unchecked] unchecked cast found : java.lang.object[] required: E[] elements = (E[]) new Object[ARRAY_SIZE]; 1 warning 이 경고 메시지는 물론 실제로는 Object 배열인 elements 멤버 필 드를 제네릭 자료형인 E 자료형의 배열로 강제 형 변환을 했을 때, 형이 맞지 않아서 발생하는 일을 컴파일러는 책임질 수 없다는 뜻이 다. 하지만 자바가 실행 시간에는 제네릭 자료형 정보를 가지고 있지 않고, 또 MyVector 클래스는 제네릭 자료형이 String이든 Integer 이든 실행 시간에는 모두 동일한 클래스로 간주되므로, 실제로 멤버 필드인 elements의 자료형은 모든 객체의 부모 클래스인 Object 배 열로 처리된다. 여기에 대해서는 다시 설명할 것이다. 따라서 첫 번째 제네릭 클래스인 MyVector 클래스는 제네릭스에 관한 한 최선을 다 해 정확하게 선언된 셈이다. 제네릭스 들여다보기 자료형 지우기 자바 제네릭스는 앞에서 살펴본 대로 만들어진 클래스를 사용하기에 는 코드에서 강제 형 변환을 많이 사라지게 하고, 버그의 가능성을 줄 여주는 멋진 친구이지만, 직접 만들어 사용하기에는 상당히 불편한 녀석이다. 이것은 제네릭스의 구현 방법과 무관하지 않은데, 자바 제 네릭스는 자료형 지우기(type erasure)라는 접근 방법으로 구현되었 다. 자료형 지우기는 간단하게 표현하자면 컴파일러가 컴파일시에 제 네릭 자료형에 대한 정보를 모두 검사하고 이를 통과할 경우 제네릭 자료형 정보가 전혀 없는 바이트코드를 생성하는 방식이다. 따라서 다음은 true 를 출력한다. List<String> list1 = new ArrayList<String>(); List<Integer> list2 = new ArrayList<Integer>(); List list3 = new ArrayList(); System.out.println(list1.getClass() == list2.getclass() && list2.getclass() == list3.getclass()); 클래스는 모두 공유하지만 실제 클래스의 제네릭 자료형 변수 값은 각 객체 인스턴스별로 달라질 수 있으므로 같은 클래스 내에서 공유 되는 static 문맥에서는 클래스에 선언된 제네릭 자료형을 참조할 수 가 없다. 즉, static 변수, static 초기화 블럭, static 메쏘드 등에서 클래스에 선언된 제네릭 자료형을 사용할 수가 없다. 마찬가지 이유 로 다음 표현식은 컴파일 에러를 발생시킨다. List<String> list = new ArrayList<String>(); 204
System.out.println(list instanceof ArrayList<String>); // 컴파일 에러 즉, 실행시에는 제네릭 자료형에 대한 정보가 없으므로, instan ceof 연산자를 제네릭 자료형에 대해 실행할 수 없는 것이다. 자료형 지우기와 반대의 구현 방법으로는 흔히 구상화(reification)라고 부르 는 방법으로 바이트코드에 제네릭 자료형에 관련된 정보를 실제로 생 성하는 방법이 있다. C#이 이러한 방식으로 1.1 버전에서 제네릭스 를 구현했다고 하며, 자바에서는 기존 애플리케이션이나 라이브러리 와의 호환성을 최우선으로 고려하여 자료형 지우기 방식을 채택했다 고한다. 제네릭스 와일드카드 자바 제네릭스가 C++의 템플릿과 유사하다고 생각했던 사람들에 게 또 하나의 일탈을 느끼게 해주는 것이 바로 이 와일드카드 제네릭 자료형일 것이다. 와일드카드 자료형은 제네릭 자료형을 선언할 때 제네릭 자료형을 임의의 자료형 혹은 클래스 상속 계층 구조상의 특 정 범위를 지정할 수 있게 해준다. 자바 제네릭스에서 와일드카드는? 문자로 표시된다. 다음과 같 이 와일드카드인? 문자로 표시된 제네릭 자료형은 임의의 모든 자 료형을 가리킨다. 쏘드를 호출하지 않는다는 점이다. 실제 wclist 변수 안에 String 리 스트가 대입되어 있다고 하더라도, 컴파일러는 add() 메쏘드의 시그 너처가 원래 add(e)였으므로, E가?로 적용되어 있으므로 add(?)로 간주하고 입력된 인자의 자료형이? 이기를 기대한다.? 은 임의의 객체이므로 자바 컴파일러는 String을 받아들일 수 있다는 확신을 하 지 못한다. 만약 입력될 수 있는 객체 자료형의 범위가 String이거나 String의 부모 클래스로 제한된다면, 자바 컴파일러는 이 경우 String을 받아들일 수 있다고 판단한다. // add 메쏘드가 입력 변수로 제네릭 변수를 받으므로 하한 경계의 와일드카드를 사용함 List<String> slist = new ArrayList<String>(); List<? super String> wclist = slist; wclist.add( wild ); wclist.add( card ); for (Object o : wclist) { // iterator()의 결과값은 Object로만 처리 System.out.println( value = + o); 일반적으로 자바 컴파일러는 입력 변수로 주어진 제네릭 자료형에 대해서는 와일드카드의 하한 경계를 지정함으로써 자료형의 제약을 풀 수 있고, 반환 유형으로 주어진 제네릭 자료형에 대해서는 와일드 List<?> wclist = new ArrayList<String>(); List<?> wclist2 = new ArrayList<Integer>(); import java.util.*; <리스트 3>은 와일드카드를 사용한 List의 예이다. 와일드카드로 표현된 제네릭 자료형을 가진 wclist 변수는 String 리스트와 Integer 리스트를 모두 받을 수 있다. 자바 제네릭스의 와일드카드는 임의의 객체를 표현할 뿐만 아니라 클래스 상속 계층 구조상의 경계를 지정할 수 있다. 이를 위해 super 와 extends라는 두 예약어를 사용한다. public class WildcardList { List<String> slist = new ArrayList<String>(); slist.add( hello ); slist.add( world ); List<Integer> ilist = new ArrayList<Integer>(); ilist.add(1); ilist.add(2); List<? extends Number> List<? super Integer> extends를 사용하는 와일드카드는 흔히 와일드카드의 상한을 지 정한다고 하는데, 그 의미는 extends 다음에 나오는 클래스를 포함하 여 그 자식 클래스들이 제네릭 자료형으로 올 수 있음을 나타낸다. super를 사용하는 와일드카드는 그 반대로 와일드카드의 하한을 지정하는데, 그 의미는 super 다음에 나오는 클래스를 포함하여 그 부모 클래스들이 제네릭 자료형으로 올 수 있음을 나타낸다. 앞의 소스코드에서 눈여겨 볼 점은 wclist 변수에서 직접 add() 메 List<?> wclist = slist; for (Object o : wclist) { System.out.println( value = + o); wclist = ilist; for (Object o : wclist) { System.out.println( value = + o); 205
카드의 상한 경계를 지정함으로써 자료형의 제약을 풀 수 있다. // iterator 메쏘드가 반환 변수로 제네릭 변수를 주므로 상한 경계의 와일드카드를 사용함 List<? extends String> wclist2 = slist; for (String s : wclist2) { // 상한 경계 덕분에 String 사용 가능 System.out.println( string = + s); extends와 super를 사용하여 와일드카드의 경계를 정하는 것에서 한 가지 유추해볼 만한 사실은 다음이다. 앞에서 List<String> 변수에 ArrayList<String> 변수를 대입하는 것은 자연스러웠다. 즉, ArrayList<String>은 List<String>의 자식 자료형이며 List<String>으로 취급할 수 있다. ArrayList 클래스가 List 인터페이스를 구현하고 있으므로(자식 자료형이므로) 이것은 합리적이다. 하지만 ArrayList<String> 변수에 ArrayList<Object> 변수를 대입하는 것은 에러가 발생한다. 즉 ArrayList<String>은 ArrayList<Object>의 자식 자료형이 아니며 ArrayList<Object>로 취급할 수 없다는 것이다. 그렇기 때문에 ArrayList<String>과 ArrayList<Object>를 모두 취급 가능한 제네릭스 형태는 ArrayList<Object>가 아니라 ArrayList<? super String>이 되는 것 이다. 제네릭 메쏘드 자바 제네릭스는 클래스나 인터페이스와 같은 자료형을 선언할 때 클 래스나 인터페이스의 변경 가능한 자료형 변수로 지정하는 경우 외에 도 메쏘드에서도 사용할 수 있다. 클래스와 인터페이스에서 제네릭 자료형 변수를 사용할 때와 메쏘 드에서 사용할 때에는 조금 의미가 다르다. 앞에서 MyVector 클래스 를 선언할 때 MyVector 클래스에서 사용하는 제네릭 자료형 변수인 E가 멤버 필드나 멤버 메쏘드에 사용될 때, 이 E 자료형은 모두 동일 한 자료형을 뜻하였다. 즉, 제네릭 자료형에 대입된 실제 클래스가 String이면 멤버 필드의 E 자료형도 String으로 간주되고, 멤버 메쏘 드의 인자나 반환 유형으로 사용된 E 역시 String으로 간주되었다. 이 점을 유념하면서 제네릭 메쏘드를 알아보자. 제네릭 메쏘드는 메쏘드를 선언할 때 메쏘드 시그너처 앞 부분에 꺾쇠괄호 안에 제네릭 자료형 변수를 선언한다. 만약 제네릭 메쏘드 에 사용된 제네릭 자료형 변수가 와일드카드라면 별도로 선언할 필요 가 없다. 다음 예에서는 T가 제네릭 자료형 변수로 선언되었다. public static <T> T genericmethod(t a, Collection<T> b) { //... <리스트 4>는 두 개의 제네릭 메쏘드 getone()과 getone Element()를 예시하고 있다. getone() 메쏘드의 경우, 제네릭 자료형 타입인 T가 두 개의 인자 와 반환 유형 세 군데서 사용되고 있다. 이 경우 각 인자 T에 적용되 는 자료형이 반드시 같은 필요는 없는데, 자바 컴파일러는 실제 이 메 쏘드를 호출하는 곳에서 인자들의 자료형을 확인해서 제네릭 자료형 타입 변수에 들어갈 실제 자료형을 유추하는 기능을 제공한다. 예제의 경우, getone() 메쏘드에 사용된 두 개의 인자가 하나는 List<String> 자료형이고, 다른 하나는 Set<Integer> 자료형이다. 자 바 컴파일러는 이 두 자료형의 공통 부모 클래스인 Collection을 유추 해내고, 또 String과 Integer를 모두 처리할 수 있는 와일드카드인? 를 해당 Collection의 제네릭 자료형으로 유추해낸다. 즉, 이 경우 에는 T가 Collection<?>으로 결정된다. 결과 값 역시 T 자료형이므로 getone() 메쏘드를 호출한 결과값을 Collection<?> 자료형의 객체에 import java.util.*; public class PolymorphicMethod { private static boolean toggle = false; List<String> list = new ArrayList<String>(); list.add( hello ); list.add( world ); Set<Integer> set = new HashSet<Integer>(); set.add(1); set.add(2); Collection<?> col = getone(list, set); System.out.println(col); Object el = getoneelement(list, set); System.out.println(el); static <T> T getone(t a, T b) { toggle =!toggle; return toggle? a : b; static <T, U> Object getoneelement(list<t> a, Set<U> b) { toggle =!toggle; return toggle? a.get(0) : b.iterator().next(); 206
저장하면 아무런 경고 없이 컴파일된다. 두 번째 제네릭 메쏘드인 getoneelement() 메쏘드는 여러 개의 제네릭 자료형 타입을 선언하는 예를 보여주고 있다. 제네릭스와 배열 배열은 자료형 중에서 조금 특이하면서도 까다로운 존재이다. 코드에 서 보는 대로 현재 자료형이 비록 Object 배열로 선언되어 있다 하더 라도 배열을 생성할 때 String의 배열로 생성하였다면, 배열의 원소 로 String이 아닌 Object 객체를 넣을 수가 없다. Object[] objarray = new String[1]; objarray[0] = new Object(); // 실행 시간 에러! 이러한 배열의 특성 때문에 자바 제네릭스에서 배열의 컴포넌트 자 료형만을 제네릭 자료형 변수로 사용할 수가 없다. 즉, 다음은 허용되 지 않는다. 다만 컴포넌트 자료형에 상하한 제약 없는? 와일드카 드를 쓰는 것은 허용된다. List<String>[] list = new List<String>[10]; // 제너릭 자료형 객체의 배열. 허용 안됨. List<?>[] list = new List<?>[10]; // 제약 없는 와일드카드 제너릭 자료형 객체의 배열. 허용됨. public <T> T[] toarray(t[] a) { // 제너릭 자료형 배열. 허용됨 형태의 자바 메쏘드이다. 브리지 메쏘드는 오버라이드한 메쏘드가 부 모 클래스와 동일한 메쏘드 시그너처를 가지고 있지만 반환 자료형이 다를 경우에 발생한다. <리스트 5>에서 CoB 클래스는 CoA 클래스를 상속하면서 crea te() 메쏘드를 오버라이드한다. 이때, 반환 유형이 부모 클래스와 달 리 CoA가 아닌 CoB를 반환하도록 선언하였다. 이 코드를 컴파일한 후 역컴파일해 보면 CoB 클래스에는 부모로 부터 물려받은 CoA를 리턴하는 create() 메쏘드가 여전히 존재함을 볼 수 있다. 이 상속받은 create() 메쏘드는 자식 클래스에서 재정의 한 CoB를 리턴하는 create() 메쏘드를 호출해주는 다리 역할만을 수 행한다. CoB 클래스 파일 포맷을 분석해 보면 소스코드에는 없었던 이 브 리지 메쏘드에 대한 플래그 값이 0x40, 0x1000 값이 설정되어 있음 을 볼 수 있다. 0x40은 브리지 메쏘드를 표현하는 플래그이며 0x1 000은 소스코드에는 없이 인위적으로 생성한 메쏘드(synthetic method)임을 표현하는 플래그이다. 브리지 메쏘드 역시 자바 5.0에서만 지원되는 개념이며, 자바 컴파 일러에 의해 자동으로 생성된다. 또, 오버라이드한 메쏘드에서 반환 자료형을 부모 클래스의 반환 자료형보다 좀 더 엄밀한 자료형, 즉 자 식 자료형으로 정의하는 것도 자바 5.0 이후 버전에서만 지원되는 기 능이다. 언뜻 생각하기에 자료형 안정성 검사를 컴파일시에 강화하는 자바 제네릭스의 취지에 비추어 이러한 배열의 자료형 검사 문제에 대해서 도 개선이 있을 듯도 하지만, 적어도 자바 5.0의 제네릭스는 별다른 해법을 제시하지 않는다. 브리지 메쏘드 브리지 메쏘드는 제네릭스를 구현하는 방법에 의해 사용되는 독특한 javap -c CoB <엔터> Compiled from CovariantReturn.java class CoB extends CoA{ CoB(); Code: 0: aload_0 1: invokespecial #1; //Method CoA. <init> :()V 4: return class CoA { public CoA create() { return new CoA(); class CoB extends CoA { public CoB create() { return new CoB(); public CoB create(); Code: 0: new #2; //class CoB 3: dup 4: invokespecial #3; //Method <init> :()V 7: areturn public CoA create(); Code: 0: aload_0 1: invokevirtual #4; //Method create:()lcob; 4: areturn 207
이 기능은 Iterator 인터페이스의 next() 메쏘드처럼 제네릭 자료 형으로 반환 자료형이 선언되어 있는 경우, 구현 Iterator 클래스들 의 상속 관계와 무관하게 정확한 제네릭 자료형을 반환하도록 선언해 야 하는 필요성에 의해 채택되었다. 런타임 제네릭스 실행 시간에 아무런 제네릭 자료형 정보를 남기지 않는 자료형 지우 기 방식의 특성 때문에 자바 제네릭스의 접근 방식에 대해 비난과 조 롱이 적지 않았다. 예를 들어, 제네릭 자료형을 사용하여 객체를 생성하거나 제네릭 자료형을 컴포넌트 자료형으로 가지는 배열을 생성하거나 하는 일은 C++ 템플릿에 익숙해진 프로그래머에게는 너무나 당연한 일이지 만, 자바 제네릭스로는 어려운 일이 된다. 이에 대한 자바 5.0이 내놓 은 해법은 java.lang.class 클래스를 활용하는 것이다. 제네릭스가 적용된 자바 5.0의 클래스들은 대부분 컬렉션 라이브 러리 클래스들이지만, Class 클래스를 포함하여 ThreadLocal, WeakReference 등이 추가로 있다. Class 클래스는 자바 5.0의 제네 릭스에서 좀 독특한 역할을 수행하는데, 이 정보는 실행 시간까지 살 아 있게 된다는 점이 다른 제네릭스와 크게 다른 점이다. Class<T>와 같이 제네릭스 방식으로 표현된 인자가 String.class를 넘겨 받으면 T는 String이 된다. 즉, 다음과 같이 객체를 생성할 수 있다. public static <T> T createobject(class<t> clazz) throws Exception { return clazz.newinstance(); 켜고 컴파일을 하는 것이 현명할 것이다. Note: Recompile with -Xlint:unchecked for details. 제네릭스는 직접 제네릭스를 지원하는 자료 구조를 만들려고 하지 않는다면 기쁘게 사용하면 되는 선물일지도 모른다. 하지만, 프로그 래밍이라는 것이 어느 한쪽 켠에만 숨어지내게 하지 않는다. 결국 제 네릭스의 깊은 부분, 어쩌면 자바 제네릭스의 어두운 부분과 맞닥뜨 릴일이있을것이다. 자바 5.0은 놀라운 수행 성능 개선을 보이면서 멋지게 다가왔다. 제네릭스가 온통 유쾌한 언어 기능이 아닐지는 모르지만, 자바 5.0의 멋진 면모를 보여주는 중요한 요소임에 분명하다. 기대에 조금 못 미 치는 면도 있고, 사실 불필요하게 어려워진 부분도 있지만, 도구는 결 국 활용하는 사람에 의해 그 가치가 드러나는 법이므로 자바 5.0이라 는 도구를 최대한 활용하여 좋은 소프트웨어를 만들고, 또 다음 버전 에는 더 나은 기능들이 채택될 수 있도록 노력하면 될 것이다. 정신없이 바쁘게 살다보니 벌써 한 해가 저문다. 코더로서, 소프트 웨어 엔지니어로서 살아가는 게 정말 녹녹한 일이 아니다. 개인적으 로는 XML과 웹 서비스, 비즈니스 프로세스의 수많은 스펙들과 또 수많은 구현들과 씨름한 한 해였다. 여러 오픈소스 프로젝트들에서 볼 수 있듯이, 능력 뛰어난 한두 명의 헌신에 따라 수많은 소프트웨어 가 명멸하였다. 독자 제위들도 한 해 잘 마무리하고 내년 한 해도 좋 은 소프트웨어를 만들길 빈다. a m o s 유사한 방법으로 배열도 생성할 수 있다. 이 경우에는 Array 클래 스의 newinstance() 메쏘드가 Object를 리턴하기 때문에 경고를 피 할 수 없다는 것이 단점이다. generics.zip http://www.imaso.co.kr public static <T> T[] createobjectarray(class<t> clazz, int length) throws Exception { return (T[]) java.lang.reflect.array.newinstance(clazz, length); 유쾌, 불쾌 뒤섞인 제네릭스 자바 5.0을 사용하면서 제네릭스는 전면에 와닿는 문제이다. 코드의 상당 부분에 꺾쇠괄호가 채워지게 될 것이고, 또 컴파일할 때마다 강 화된 자료형 검사에 당황하게 될 것이다. 그리고 자바 5.0으로 개발 을 하는 동안 결코 유쾌하지 않은 경고 메시지와 종종 만나게 될 것이 다. 이 경고 메시지는 실제 잘못된 형 변환을 지적할 수도 있으므로 쉽게 무시해서는 안 된다. 가능하면 -Xlint:unchecked 기능을 항상 Generics in the Java Programming Language, Gilad Bracha, July 2004 http://java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf Java Specification Requests 14 : Add Generic Types To The JavaTM Programming Language http://jcp.org/en/jsr/detail?id=14 Puzzling Through Erasure, Bruce Eckel, Sep., 2004 http://mindview.net/weblog/log-0057 Puzzling Through Erasure : answer section, Neal Gafter, Sep., 2004 http://gafter.blogspot.com/2004/09/puzzling-through-erasureanswer.html Proposed Changes to the Java Virtual Machine Specification, chapter 4 The class file format http://java.sun.com/docs/books/vmspec/2nd-edition/classfileformatfinal-draft.pdf 208