Java 제네릭(generic) 이란
자바의 제네릭(Generic)에 대해 알아보겠습니다.
제네릭은 데이터의 타입을 일반화 해주며, 컴파일 시에 타입이 정해집니다.
예를 들어, ArrayList 객체를 생성할 때 <>안에 타입 파라미터를 정의하여 ArrayList에 어떤 타입의 데이터를 담을 것인지를 명시합니다.
List<Integer> numberList = new ArrayList<>();
ArrayList의 클래스를 보면 ArrayList<E>로 제네릭을 사용하여 정의가 되어 있는데,
위와 같이 Integer 타입 파라미터를 정의하여 객체를 생성하면, 컴파일 시에 제네릭은 E는 Integer 타입이 되고,
add함수는 add(Integer e)가 되어 Integer만을 받아 arrayList의 엘레멘트에 추가를 합니다.
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
...
transient Object[] elementData;
...
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
...
}
JDK 1.5 이전에는 여러 타입을 사용해야 하는 경우 Object 타입을 사용했지만
이를 원하는 타입으로 변경할 때 오류가 발생할 수 있어 JDK 1.5부터 제네릭이 도입 되었습니다.
컴파일 시점에 타입이 정해지므로, 타입 검사 및 변환 작업을 생략할 수 있고 전환하며 발생할 수 있는 오류도 방지할 수 있습니다.
제네릭은 클래스, 인터페이스, 메소드를 정의할 때 사용할 수 있습니다.
Generic 클래스, 인터페이스
타입 파라메터에 들어간 'T'로 제네릭으로 명시를 하였습니다.
( T가 아닌 어떤 문자로도 타입 파라미터 정의가 가능하지만, 일반적으로 T를 많이 사용합니다. )
public class Class<T> { ... }
public interface Interface<T> { ... }
public class Corn<T>{
private T t;
public T get();
public void set(T t);
}
제네릭 Type을 지정하지 않고 Object 타입을 사용하면 모든 객체를 저장할 수는 있지만, 앞서 설명 드린것 처럼 읽고 저장할 때 타입 변환을 해야하고, 변환 시 오류가 발생할 수 있습니다.
콤마로 타입을 나열하여 두개 이상의 제네릭 멀티 타입 파라미터를( class<K,V,...> interface<K,V,...> ) 사용 할 수도 있습니다.
Generic 메소드( <T,R> R method(T t) )
타입 파라미터를 함수 인자와 리턴 값으로 가지는 메소드입니다.
먼저 타입 파라미터를 명시하고, 리턴 타입 + 메소드먕 + 매개변수 로 함수를 정의합니다.
public <타입파라미터, ...> 리턴타입 메소드명(매개변수, ...) { ... }
public <T> Corn<T> makePopCorn(T t) { ... }
// 제네릭 메소드 호출 방법
Corn<Integer> = <Integer>makePopCorn(10); // 명시적으로 Integer를 지정
Corn<Integer> = makePopCorn(10); // Integer로 추정 (컴파일러가 매개 값을 보고 타입 추정)
타입 파라미터 제한( <T extends 최상위 타입> )
제네릭에서는 모든 타입을 사용할 수 있지만, extends 키워드를 사용하여 특정 타입만 사용하도록 제한할 수 있습니다.
예를 들어 숫자를 연산하는 제네릭 메소드는 매개변수 값으로 Number 타입이나 하위 클래스 타입(Byte, Short, Integer, Long, Double)의 인스턴스만 가져야 하는 경우, 'extends Number'로 Number 및 하위 타입의 파라미터만 받을 수 있도록 제한할 수 있다.( 클래스, 인터페이스, 메서드 모두에 사용 가능 )
public <T extends Number> int compare(T t1, T t2){
double v1 = t1.doubleValue();
....
}
와일드카드 타입( <?>, <? extends ..>, <? super ...> )
어떤 타입도 사용할 수 있도록 특정 타입에 제한을 두지 않는 와일트카드 타입( ? 기호 )이 있습니다.
<?>
: 제한 없음
: 모든 클래스나 인터페이스 타입을 사용할 수 있습니다.
<? extends T>
: 상위 클래스 제한( 자손 클래스 가능 )
: T 타입과 T 타입을 상속받는 자손 클래스 타입만 사용할 수 있습니다.
<? super T>
: 하위 클래스 제한( 조상 클래스 가능 )
: T 타입과 T 타입이 상속받은 조상 클래스 타입만 사용할 수 있습니다.