제네릭이란?
컴파일시에 타입을 체크함으로써 코드의 안정성을 높여주는 기능
- 컴파일시 타입체크를 진행하여 강력한 타입 검사 실시
- 타입 캐스팅을 자동으로 해줌
예시 (ArrayList)
ArrayList<String> arr = new ArrayList<String>();
arr.add("abc");
arr.add(new Integer(2)); // error
제네릭는 <>를 사용해서 타입을 지정해준다.
위의 ArrayList는 으로 제네릭를 지정했다. 그래서 arr 객체에 String 클래스가 아닌 클래스는 추가할 수 없다.


ArrayList 클래스를 보면 같은 제네릭를 볼 수 있다. E를 타입 변수라고 하고 주로 E나 T를 자주 사용한다. Map같이 두 변수를 사용하는 경우에는 <K,V>와 같은 형식으로 두개를 사용한다. 우리가 ArrayList를 생성할 때 을 넣은 것처럼 클래스를 입력해주면 E 값이 입력한 클래스로 변경된다. 그래서 형변환 없이 입력한 클래스의 메서드를 사용할 수 있는 것이다.
이처럼 제네릭가 붙은 클래스를 '제네릭 클래스(타입)'라 하고 일반 클래스를 '원시 타입'라고 부른다.
제네릭의 규칙
1. 인스턴스를 생성할 때는 제네릭타입이 일치해야 한다. (와일드 카드 제외)
ArrayList<Object> arr = new ArrayList<Object>();
ArrayList<Object> arr = new ArrayList<String>(); //error
상속 관계에 있더라도 생성이 불가능하니 주의하자.
2. 생성된 객체에는 자손 객체의 입력이 가능하다.
arr.add("abc"); // 가능
제네릭 타입을 제한하는 방법
이렇게 두면 어떤 클래스든 입력이 가능하다.
객체를 생성하면 타입을 제한할 수 있지만 특정 타입의 객체만 생성하고 싶다면 어떻게 해야할까?
제네릭 타입을 제한하기 위해 extends나 super를 사용한다.
<T extends Guitar>는 Guitar 클래스를 포함한 그 자손 클래스만 입력이 가능하다는 뜻이다.
super는 아래 와일드 카드 부분에서 설명하겠다.
와일드 카드
제네릭 클래스 객체 중에서 특정 타입만을 허용하고 싶을 때 와일드 카드를 사용한다.
와일드 카드를 사용하면 제네릭 클래스의 객체를 생성할 때 제네릭 타입에 자손 클래스를 입력할 수 있다.
- <? extends Guitar> : Guitar와 그 자손들 가능
- <? super Guitar> : Guitar와 그 조상들 가능
- <?> : 제한 없음.
public static void main(String[] args) {
Test(new ArrayList<String>());
Test(new ArrayList<Integer>());
}
public static void Test(ArrayList<? extends Object> a){
System.out.println("성공");
}
// 성공
// 성공
이처럼 와일드 카드를 사용하면 제네릭 타입에도 다형성을 적용할 수 있다.
사용 예시
public class Run {
public void popTest(Category<? extends Guitar> guitar) {
Guitar g = guitar.get();
guitar.set(new Guitar()); // compile error
}
public void pushTest(Category<? super Guitar> guitar) {
Guitar g = guitar.get(); // compile error
guitar.set(new Guitar());
}
}
- 상한경계(extends)를 사용하는 경우는 set이 불가능하다.
- 입력하는 객체의 타입이 상위 클래스일 가능성이 있기 때문
- 하한경계(super)를 사용하는 경우는 get하여 경계 클래스로 참조하는 것이 불가능하다.
- get하는 객체가 경계 클래스보다 상위 클래스일 가능성이 있기 때문
그럼 언제 상한과 하한을 사용해야 할까?
- 상한 경계
- 타입을 안전하게 가져올 수 있음
- 무언가를 생성하는 경우에는 상한경계를 사용하는 것이 적절
- 하한 경계
- 타입을 안전하게 저장할 수 있음
- 소비를 하는 경우에는 하한경계를 사용하는 것이 적절
class Category<E> {
private List<E> list = new ArrayList<>();
public void pushAll(Collection<? extends E> box) {
for (E e : box) {
list.add(e);
}
}
public void popAll(Collection<? super E> box) {
box.addAll(list);
list.clear();
}
}
제네릭의 제약
1. 스태틱 변수, 메서드에는 타입 변수를 사용할 수 없다. (제네릭 메서드 제외)
class Box<T> {
static T item; // error
static int compare (T t1,T t2); // error
}
2. 배열 생성이 불가능하다.
T[] arr = new T[3]; // error
new 연산자는 컴파일 시점에 타입을 정확히 알아야하기 때문이다.
제네릭 메서드
메서드의 선언부에 제네릭 타입이 선언된 메서드를 의미한다.
메서드의 매개변수의 타입을 지정한다.
선언부의 T와 매개변수 T가 일치해야 한다.
static <T> void sort(...<T>...){...}
이 타입 매개변수는 클래스의 타입 매개변수와는 독립적이다.
제네릭 메서드의 타입 매개변수는 제네릭 메서드 내에서만 유효하다. 지역변수라고 생각하면 쉽게 이해가 된다.
제네릭 타입의 제거
제네릭은 컴파일시 필요한 형변환을 넣어주고 지워진다.
정리
- 제네릭은 컴파일 시 강력한 타입 체크를 위한 도구이다.
- <T extends Guitar>와 같이 타입을 제한하면 원하는 타입의 객체만 생성 가능하다.
- <? extends Guitar>는 생성된 객체의 참조하는 경우 다형성을 제공해준다.
참고
자바의 정석 기본편
'Java > 개념' 카테고리의 다른 글
| 스트림 (Stream) (0) | 2023.01.18 |
|---|---|
| 람다식 (Lamnda Expression) (0) | 2023.01.17 |
| (Collection) Map (0) | 2023.01.15 |
| (Collection) Set (0) | 2023.01.15 |
| (Collection) Iterator, Comparator, Comparable (0) | 2023.01.15 |