본문 바로가기

프로그래밍/Java

JAVA 1.5 제네릭(GENERIC) 사용법

J2SE 5.0 버전은 Java 플랫폼에 대해 중요 언어 수준의 변경을 다소 포함하고 있다.

또한 J2SE 5.0은 루프와 매개변수의 강화와 같은 새로운 작업을 추가하여

JSR-14: Add Generic Types To The Java Programming Language와 일치하는 제네릭(generic)을 통해

Java Collections 프레임워크와의 컴파일 타임 타입 안정성을 제공한다.

제네릭의 기본적인 사용법 중 하나는 컬렉션(collection)과 함께 사용할 때 데이터를 추상화하는 것이다.

JDK 5.0 이전 버전에서는 Collection 생성시, 컬렉션 내부에 임의의 값을 줄 수 있었다. 예를 들면, 다음과 같다.

List myList = new ArrayList(10);
myList.add(new Integer(10));
myList.add("Hello, World");
사용자의 Collection을 특정 타입으로 제한하는 것은 어려운 일이며, 컬렉션으로부터 하나의 아이템을 뽑아내기 위해서는

캐스팅(Casting) 연산이 필요하다.

Integer myInt = (Integer)myList.iterator().next();

만약 잘못된 타입으로 캐스팅을 한 경우에는 컴파일만 성공하고, 런타임시 예외가 발생하게 된다.

특별히 Collection 내의 모든 아이템을 Object로 처리하지 않는다면, 캐스팅은 보통 묵시적으로 발생하거나

캐스팅된 연산을 호출하기 전에 instanceof를 수행하여 발생한다.

Iterator listItor = myList.iterator();
Object myObject = listItor.next();
Integer myInt = null;
if (myObject instanceof Integer) {
   myInt = (Integer)myObject;
}
이러한 상황은 제네릭의 미학적 가치를 떨어뜨린다.

제네릭을 이용하면 하나의 Collection에서 저장하고자 하는 오브젝트의 타입을 컴파일시 지정할 수 있다.

이렇게 되면 리스트에서 아이템을 추가하거나 검색할 때, 그 리스트가 특정 타입의 오브젝트가 활성화 될 것인지를

이미 알 수 있다. 따라서 캐스팅을 할 필요가 없는 것이다.

"<>" 문자는 어떤 타입이 저장될 것인지를 지정하는데 사용합니다.

만약 잘못된 타입의 데이터가 주어진다면, 컴파일시 예외가 발생한다. 예를 들어, 다음 클래스를 컴파일하려 하면,

import java.util.*;

public class First {
   public static void main(String args[]) {
      List myList = new ArrayList(10);
      myList.add(10);
      myList.add("Hello, World");
   }
}
다음의 에러가 발생한다.

First.java:7: cannot find symbol
symbol : method add(java.lang.String)
location: interface java.util.List<java.lang.Integer>
myList.add("Hello, World");
^
1 error

즉, 이 메시지는 기본적으로 해당 인터페이스가 Integer 오브젝트의 List에 대한 것일 때 a

dd(String) 메소드는 사용할 수 없다는 것을 의미한다.

myList.add(10); 메소드는 리스트에 Integer 오브젝트 10을 add 하는 작업을 수행하였고,

Autoboxing은 int 타입을 Integer로 변환하였다.

여기서 실패한 것은 String을 Integer의 List에 추가하는 것 뿐이다.

하나의 컬렉션의 엘리먼트로 작업을 하면서 그 컬렉션 내의 엘리먼트의 타입을 명시적으로

선언하지 않은 경우에도 경고를 받는다.

예를 들어, 다음 프로그램은 List를 생성하지만, 그 List 내에 어떤 타입이 저장될 지에 대해서는 지정하지 않는다.

import java.util.*;

public class Second {
   public static void main(String args[]) {
      List list = new ArrayList();
      list.add(10);
   }
}

이 프로그램을 컴파일 하면, 다음과 같은 경고를 받게 된다.

Note: Second.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

그 후 지시에 따라 -Xlint:unchecked를 javac 명령문에 추가하면

javac -Xlint:unchecked Second.java

다음과 같은 추가 정보가 표시된다.

Second.java:6: warning: [unchecked] unchecked call to add(E)
as a member of the raw type java.util.List
list.add(10);
^

이 메시지는 "add(E)" ” 호출에서 "E" 라는 List에 추가된 엘리먼트의 타입이 지정되지 않았다는 뜻이다.

그러나 이 메시지는 경고일 뿐, a.class 파일은 생성된다.

경고를 제거하고 싶으면 다음과 같이 <Object>를 List 선언시 추가하면 된다.

List<Object> list = new ArrayList<Object>();

여기서, Object는 E이고, 기본적으로 그 List에는 어떤 타입도 저장 가능하다는 것을 의미한다.

그런데 E는 어디서 온 것일까?

java.util.List에서 인터페이스 선언부를 보면, 다음의 내용을 볼 수 있다.

public interface List<E> extends Collection<E>

이것은 문법적으로 E의 List를 하나 선언한 것이다.

나중에 인터페이스 선언시, 매개변수나 리턴 타입이 E로 교체된 메소드를 볼 수 있을 것이다.

Iterator<E" iterator();
ListIterator<E" listIterator();
boolean add(E o);
boolean addAll(Collection<? extends E" c);

E는 컴파일시 지정되기 때문에 타입 불일치를 찾기 위해 런타임 예외에 의존할 필요가 없다.

addAll 라인의 ?는 알려지지 않은(unknown) 컬렉션으로 생각될 수 있지만, unknown은 E일 수도 있고, 서브클래스일 수도 있다.

이러한 구성으로 인해 정확한 타입 매칭만을 사용하지 않고도, 서브클래스들의 컬렉션으로 작업할 수 있다.

첫 번째 예제를 통해서 완벽하게 알 수는 없겠지만, 제네릭은 일반적으로 이터레이터(iterator)를 이용해

루프 강화(enhanced for loop )와 함께 사용된다.

제네릭이 없을 경우, String 오브젝트의 하나의 List를 루핑하려면 각각의 엘리먼트를 List로부터 적절한 타입으로 변환해야 한다.

그러나 제네릭과 루프 강화를 이용하면 모든 것이 간단해진다

다음은 제너릭과 루프 강화 없이 String 오브젝트들의 List를 루핑하는 기존 방법이다.


import java.util.*;

public class Old {
   public static void main(String args[]) {
      List list = Arrays.asList(args);
      Iterator itor = list.iterator();
      while (itor.hasNext()) {
         String element = (String)itor.next();
         System.out.println(element + " / " + element.length());
      }
   }
} 
위의 Old 클래스를 컴파일/실행하고 다음과 같이 하나의 문자열을 매개로 실행하면,

java Old Hello

다음 결과를 얻게 된다.

Hello / 5

JDK 5.0을 이용하면 최신의 루프 구조 강화와 제네릭을 결합하여 컴파일시 타입 안정성이 보장되는 프로그램을

생성할 수 있으며, 가독성과 관리 용이성이 더 향상된다.

다음은 제네릭과 루프 강화를 사용할 경우 String오브젝트들의 List 전체 루핑이 어떻게 되는지 보여주는 예제이다.

import java.util.*;

public class New {
   public static void main(String args[]) {
      List list = Arrays.asList(args);
      for (String element : list) {
         System.out.println(element + " / " + element.length());
      }
   }
}

java New Hello
Hello / 5

이와 같이, 제네릭과 루프 강화는 서로 잘 동작한다.

제네릭에 대한 좀 더 상세한 정보는 다음을 참조 하기 바란다.

* Tutorial: Generics in the Java Programming Language (pdf)
* JSR-14: Add Generic Types To The Java Programming Language
* Generics

 

출처 : http://www.javastudy.co.kr/