메서드나 클래스를 선언할 때 입력 받을 값을 파라미터로 선언하듯이 파라미터의 자료형을 제너릭을 활용해 받을 수 있다.
예를 들어 입력받을 자료형이 정해지지 않은 상태에서 클래스를 선언한다면 아래와 같이 작성할 수 있다.
class Data<T> {
private T var;
Data(T var) {
this.var = var;
}
T get() {
return this.var;
}
}
public class classes {
public static void main(String[] args) {
Data<Integer> data = new Data<Integer>(6);
System.out.println("value: " + data.get());
}
}
Data 클래스는 입력 받을 인자의 파라미터(var)뿐만 아니라 파라미터의 자료형도 T라는 파라미터를 통해 받을 수 있다. 위 예시에서는 Integer를 사용해 정수를 받았지만, 다른 클래스나 자료형을 사용할 수도 있다.
이렇게 제너릭을 사용하면 코드의 재사용성을 높일 수 있고, 함수를 호출할 때 자료형을 지정하기 때문에 자료형을 확인하거나 변환하는 과정이 필요하지 않다.
제너릭은 파라미터와 같이 이름을 자유롭게 정할 수 있지만 일반적으로는 아래와 같이 작성한다.
<T>: Type
<T, S, U, V ... >: Type1, Type2, Type3, Type4 ...
<E>: Element
<K, V>: Key, Value
<N>: Number
클래스
class 이름<제너릭> {...}
class Data<K, V>{...}
메서드
<제너릭> 함수 선언(...){...}
class Data {
public <E> E getElement(E element) {
return element;
}
}
메서드의 리턴 자료형 앞에 제너릭 표현을 사용한다.
제한
<자식 extends 부모>
extends를 사용하면 자식 클래스만 입력값으로 받을 수 있다. 예를 들어, 아래와 같이 Number 클래스를 확장(extends)하면 N의 입력값으로 Integer, Double 등 Number의 자식 클래스만 받을 수 있다.
class Data<N extends Number> {
private N var;
Data(N var) {
this.var = var;
}
}
extends와 반대되는 개념으로 super가 있는데, 아래와 같이 사용할 수 있다.
<부모 super 자식>
extends는 해당 클래스의 상속을 받은 클래스만 사용할 수 있도록 했다면, 반대로 super는 부모 클래스만을 사용할 수 있도록 제한한다.
따라서, extends를 '상한 경계', super를 '하한 경계'라고도 표현한다.
Wild Card
?을 이용해 타입을 표기할 수 있다. 일반적으로 타입이 크게 중요하지 않을 때 사용한다.
class Data<N extends MyNumber<? extends Integer>> {...}
MyNumber 클래스를 통해 N의 범위를 제한한다는 사실이 중요할 뿐, MyNumber의 타입이 무엇이든 중요하지 않을 때 위와 같이 표현할 수 있다.
만약 <?>만 단독으로 표기하면 모든 타입을 사용할 수 있다는 의미이다. 정확히는 Object를 상속 받은 객체가 ?로 표현된다.
class Data<N extends MyNumber<?>> {...}
class Data<N extends MyNumber<? extends Object>> {...}
static 메서드
제너릭은 타입이 정해지지 않은 상태에서 호출과 동시에 타입을 결정한다. 하지만 static 메서드는 클래스가 호출되기 전에 메모리 공간에 올라가기 때문에 주의해야 한다.
class Data<N> {
private N var;
Data(N var) {
this.var = var;
}
static N get() {
// 에러
return this.var;
}
}