자바에서 제네릭이란 데이터의 타입을 일반화한다는 것을 의미한다.
다양한 타입의 객체를 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크를 해주는 기능을 의미한다. 컴파일 시에 객체의 타입을 체크하기 때문에 다음과 같은 장점을 가진다.
클래스나 인터페이스, 메소드 내부에서 사용되는 객체의 타입 안정성을 높일 수 있다. 의도하지 않은 타입의 객체가 저장되는 것을 막고 잘못된 형변환을 막을 수 있기 때문이다.
반환값에 대한 타입 변환 및 타입 검사에 들어가는 노력을 줄일 수 있다.
제네릭 타입은 클래스와 메서드에 선언할 수 있다. 제네릭스를 활용하면 동작은 같지만 클래스 타입을 바꿔야 하는 경우를 쉽게 다룰 수 있다.
//제네릭 도입 전
public static void main(String[] args) {
List numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
int sum = 0;
for (Object number : numbers) {
sum += (int)number;
}
}
//제네릭 도입 후
//불필요한 형변환을 안해도 되며 의도하지 않은 타입이 들어오는 것을 방지할 수 있다.
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
int sum = 0;
for (Integer number : numbers) {
sum += number;
}
}
public class 클래스명<T> {...}
public interface 인터페이스명<T> {...}
제네릭 클래스(또는 인터페이스)를 선언할 때 뒤에 <T>
를 붙이는데, 여기서 <T>
는 타입 변수라고 하며 임의의 참조형 타입을 의미한다. 일종의 매개변수 같은 역할을 하면서 그 자리에 어떠한 참조형 타입이 들어가게 될 것이라는 표시를 해주는 것이다. 임의의 변수이므로 반드시 T가 아니여도 되고 상황에 따라 E, K, V 등 다양한 문자를 사용할 수 있다. 여러 개의 타입 변수는 쉼표로 구분하여 명시할 수 있다.
자주 사용되는 타입 인자 약어는 다음과 같다.
타입 | 설명 |
---|---|
<T> | Type |
<E> | Element |
<K> | Key |
<V> | Value |
<N> | Number |
<R> | Result |
public class animal<T> {
private T name;
public T getName() {
return name;
}
public void setName(T name) {
this.name = name;
}
}
이후 T
는 객체를 생성할 때, 해당 타입으로 변경된다.
public class test {
public static void main(String[] args) {
//객체 생성 시, 원하는 타입을 부여한다.
animal<String> dog = new animal<>();
//선언 시, String 타입으로 선언하여 String 값을 입력했다.
dog.setName("dog1");
System.out.println(dog.getName());
}
}
new
를 이용해 객체를 생성할 때, 원하는 타입의 속성을 넣어주면 해당 속성으로 자동으로 타입이 변환된다.
Java SE 7부터 인스턴스 생성 시 타입을 추정할 수 있는 경우에는 타입을 생략할 수 있다.
제네릭 타입 선언 시, 두 가지 이상의 제네릭 타입을 받는 것이 가능하다. 이 경우에는 추가된 제네릭 타입을 모두 지정해주어야 한다.
와일드카드 타입에는 총 3가지의 형태가 있으며 물음표(?) 키워드로 표현된다.
제네릭타입<?>
: 타입 파라미터를 대치하는 것으로 모든 클래스나 인터페이스 타입이 올 수 있다.제네릭타입<? extends 상위타입>
: 와일드카드 범위를 특정 객체의 하위 클래스만 올 수 있다.제네릭타입<? super 하위타입>
: 와일드카드 범위를 특정 객체의 상위 클래스만 올 수 있다.자바 코드에서 선언되고 사용된 제네릭 타입은 컴파일 시, 컴파일러에 의해 자동으로 검사되어 타입 변환된다. 그리고서 코드 내의 모든 제너릭 타입은 제거되어, 컴파일된 class 파일에는 어떠한 제네릭 타입도 포함하지 않게 된다. 이런식으로 동작하는 이유는 제네릭을 사용하지 않는 코드와의 호환성을 유지하기 위해서다.