제네릭 (Generics) 이란?
제네릭(Generics)은 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법을 의미한다. 객체별로 다른 타입의 자료가 저장될 수 있도록 한다.
제네릭이란 이름을 들으면 처음 사용하는 개념인 것 같지만 그렇지않다.
ArrayList<String> list = new ArrayList<>();
우리가 자바에서 흔히 쓰는 ArrayList의 <>도 제네릭이다.
제네릭은 <> 꺾쇠 괄호 키워드를 사용하는데 이를 다이아몬드 연산자라고 한다. 그리고 이 꺾쇠 괄호 안에 식별자 기호를 지정함으로써 파라미터화 할 수 있다. 이것을 마치 메소드가 매개변수를 받아 사용하는 것과 비슷하여 제네릭의 타입 매개변수(parameter) / 타입 변수 라고 부른다.
자바 컴파일러는 코드에서 잘못 사용된 타입 때문에 발생하는 문제점을 제거하기 위해 제네릭 코드에 대해 강한 타입 체크를 합니다. 실행 시 타입 에러가 나는것보다는 컴파일 시에 미리 타입을 강하게 체크해서 에러를 사전에 방지하는 것이 좋습니다. 또한 제네릭 코드를 사용하면 타입을 국한하기 때문에 요소를 찾아올 때 타입 변환을 할 필요가 없어 프로그램 성능이 향상되는 효과를 얻을 수 있습니다.
제네릭 사용법
타입 매개변수는 제네릭을 이용한 클래스나 메소드를 설계할 때 사용된다.
클래스명 옆에 기호로 제네릭을 붙여준다. 그리고 클래스 내부에서 식별자 기호 T 를 클래스 필드와, 메소드의 매개변수의 타입으로 지정한다.
class Sample<T> {
private T value; // 멤버 변수 val의 타입은 T 이다.
// T 타입의 값 val을 반환한다.
public T getValue() {
return value;
}
// T 타입의 값을 멤버 변수 val에 대입한다.
public void setValue(T value) {
this.value = value;
}
}
제네릭 클래스를 만들었으면 이를 인스턴스화 해보자. 마치 파라미터를 지정해서 보내는 것 처럼 생성 코드에서 꺾쇠 괄호 안에 지정해주고 싶은 타입명을 할당해주면, 제네릭 클래스 선언문 부분으로 가서 타입 파라미터 T 가 지정된 타입으로 모두 변환되어 클래스의 타입이 지정되게 되는 것이다.
// 제네릭 타입 매개변수에 정수 타입을 할당
FruitBox<Integer> intBox = new FruitBox<>();
// 제네릭 타입 매개변수에 실수 타입을 할당
FruitBox<Double> intBox = new FruitBox<>();
// 제네릭 타입 매개변수에 문자열 타입을 할당
FruitBox<String> intBox = new FruitBox<>();
// 클래스도 넣어줄 수 있다. (Apple 클래스가 있다고 가정)
FruitBox<Apple> intBox = new FruitBox<Apple>();
위는 제네릭 타입 전파가 행해진고 보면 된다. 부분에서 실행부에서 타입을 받아와 내부에서 T 타입으로 지정한 멤버들에게 전파하여 타입이 구체적으로 설정 되는 것이다. 이를 전문 용어로 구체화(Specialization) 라고 한다.
제네릭에서 할당 받을 수 있는 타입은 Reference 타입 뿐이다. 즉, int형 이나 double형 같은 자바 원시 타입(Primitive Type)을 제네릭 타입 파라미터로 넘길 수 없다는 말이다.
interface ISample<T> {
public void addElement(T t, int index);
public T getElement(int index);
}
class Sample<T> implements ISample<T> {
private T[] array;
public Sample() {
array = (T[]) new Object[10];
}
@Override
public void addElement(T element, int index) {
array[index] = element;
}
@Override
public T getElement(int index) {
return array[index];
}
}
public static void main(String[] args) {
Sample<String> sample = new Sample<>();
sample.addElement("This is string", 5);
sample.getElement(5);
}
class FruitBox<T> {
public T addBox(T x, T y) {
// ...
}
}
제네릭 메서드라고 하면 당연히 위와 같이 될것이라고 착각할 수 있다.
하지만 타입 파라미터로 타입을 지정한 메서드 일 뿐이다.
제네릭 메서드란, 메서드의 선언부에 가 선언된 메서드를 말한다.
class FruitBox<T> {
// 클래스의 타입 파라미터를 받아와 사용하는 일반 메서드
public T addBox(T x, T y) {
// ...
}
// 독립적으로 타입 할당 운영되는 제네릭 메서드
public static <T> T addBoxStatic(T x, T y) {
// ...
}
}
때문에 제네릭 메서드를 호출하려면 호출 메서드에도 제네릭 메서드를 작성해 주어야 한다.
FruitBox.<Integer>addBoxStatic(1, 2);
FruitBox.<String>addBoxStatic("안녕", "잘가");
이때 컴파일러가 제네릭 타입에 들어갈 데이터 타입을 메소드의 매개변수를 통해 추정할 수 있기 때문에, 대부분의 경우 제네릭 메서드의 타입 파라미터를 생략하고 호출할 수 있다.
// 메서드의 제네릭 타입 생략
FruitBox.addBoxStatic(1, 2);
FruitBox.addBoxStatic("안녕", "잘가");
참고 사이트
[간단정리] JAVA - 제네릭(Generic)이란?
Java 제네릭(Generic)이란?
☕ 자바 제네릭(Generics) 개념 & 문법 정복하기