제네릭 (Generic)
코딩을 하다보면 아무리 계획적으로 프로그래밍하고 테스트를 해도 빈번히 오류를 마주하게 된다. 그 오류가 컴파일 에러면 그나마 다행인데, 이미 코딩을 많이 진행한 상태에서 어느 시점에 발생한지 알 수 없는 실행 오류면 문제가 크다. 실행 에러는 한 마디로 프로그램이 '죽는' 것이기 때문에, 문제를 금방 찾기 어렵다. 이런 상황을 방지해주는 것이 제네릭이다.
이미지출처
제네릭은 클래스 내의 데이터 타입을 클래스 밖에서 파라미터 형태로 지정하여 데이터 타입을 일반화하는 것이다.
List myList = new List();
myList.add(new Integer(11));
Integer x = (Integer) myList.iterator().next();
제네릭 없이 코드를 짠다면 위와 같을 것이다. 이 방법도 실행시 문제는 없다.
List<Integer>
myList = new List<Integer>();
myList.add(new Integer(11));
Integer x = myList.iterator().next();
다이아몬드 연산자<>
를 활용하여 제네릭을 추가해주면, 컴퓨터는 이 List가 Integer로 이루어진 List라는 것을 알 수 있다. 코드 양이 대단히 줄어든 것은 아니지만, 제네릭을 활용하면 컴파일러가 컴파일시 올바른 타입이 사용되는지 체크를 해준다.
방대한 양의 코드를 짤 때는 제네릭 덕분에 타입체크와 형변환을 생략해서 코드가 간결해질 뿐만 아니라, 전체적인 타입 안정성이 좋아지기 때문에 가급적이면 코딩시 제네릭을 활용해서 이런 이점을 누리는 것이 효율적이다.
public class 클래스명<타입 매개변수>{...}
public interface 인터페이스명<타입 매개변수>{...}
빈번히 사용하는 타입 매개변수는 다음과 같다. 타입 매개변수는 보통 하나의 대문자로 작성하고, 제네릭 클래스는 여러 개의 타입 매개변수를 가질 수 있다.
타입인자 | 설명 |
---|---|
<T> | Type |
<E> | Element |
<K> | Key |
<N> | Number |
<V> | Value |
<R> | Result |
타입 매개변수 자리에 와일드 카드인 ?
기호를 사용하면 모든 타입이 사용 가능해진다. 때문에 타입이 일치하지 않는 다른 객체를 참조할 수 있다.
ArrayList<? extends Parent> list = new ArrayList<Children>(); // OK
ArrayList<Parent> list = new ArrayList<Children>(); // Error
다시 말해, <?>
혹은 <? extends Object>
를 쓰면 타입에 제한이 없다. extends를 사용해 <? extends T>
라고 하면, 와일드 카드에 상한 제한이 걸린 것이기 때문에, T와 그 하위/자손 타입에 제한이 생긴다. 반대로 super를 사용해 <? super T>
라고 하면 하한 제한이라 T와 그 상위/조상 타입만 가능하다.
제네릭 클래스
제네릭 클래스를 정의하기 위해서는 우선
class GenericClass<T> { }
이러한 형태를 만들어준 후,
class GenericClass<T> {
public T t;
public T get() {return t;}
public void set(T t) {this.t = t;}
}
이런 식으로 속성과 메서드를 정의해준다.
GenericClass<String> generic = new GenericClass<String>();
이후 제네릭 객체를 생성하고,
generic.set("generic exercise");
System.out.println(generic.get()); // 출력: generic exercise
제네릭 객체를 사용해보면 이러한 출력값을 얻을 수 있다.
제네릭 메서드
제네릭 클래스를 선언하는 것뿐만 아니라, 클래스 내부 특정 메서드만 제네릭으로 선언할 수 있다. 제네릭 메서드는 한 마디로 타입 변수가 선언된 메서드다.
class Example<T> {
static <T> void sort(...) { ...}
}
제네릭 클래스의 타입 변수와 제네릭 메서드의 타입 변수가 같아 보이더라도, 전혀 다른 것이다. 위 예시에서도 <T>
가 둘 다 들어있어 같은 타입 변수 같겠지만, 이는 문자만 같은 것이다. 이와 같은 이유로 내외부 클래스의 타입 문자가 같아도 별개의 것이라고 이해할 수 있다.
또한, 일반적으로 static 멤버에는 타입 매개 변수를 쓸 수 없지만, 메서드에 제네릭 타입을 선언하고 쓰는 것은 가능하기 때문에 위 코드에도 static이 붙었다. 메서드에 제네릭 타입을 선언하는 건 지역 변수를 선언한 것과 비슷하기 때문에, 어차피 지역적으로 사용될 거라 메서드가 static이건 아니건 무관하다.
제네릭 메서드와 와일드카드는 비슷한듯 완전히 다른 개념이다. 범용할 수 있는 경우도 있으나 용도에 차이가 있고, 보통 와일드카드를 쓸 수 없을 때 제네릭 메서드를 쓴다.
더 깊이 알고 싶다면 공식 문서 를 참고하자.