지네릭스
- 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시
타입 체크를 해주는 기능
- 저장된 객체를 꺼낼 때, 형변환 필요 없음
→ 어떤 타입의 객체들이 저장되어있는지 알고 있기 때문
- 생성자의 지네릭 타입은 항상 일치해야 한다.
ArrayList<Tv> tvList = new ArrayList<>();
[장점]
- 타입 안정성 제공
- 형변환 생략 가능 → 코드 간결
타입 변수
- ArrayList<E> 여기서 E는 타입 변수
- 타입 변수가 여러 개인 경우 콤마로 구분 HashMap<String, Member>
- 타입변수는 실제 타입에 대응된다. ArrayList<Tv>
public class MyList<T> {
private T[] listData; // T는 임의의 타입
}
제한된 지네릭 클래스
- 타입문자를 모든 클래스가 아닌 일부 클래스 타입으로 제한하는 방법
- < T extends E > 를 사용하는 것
E 혹은 E의 자손인 경우만 가능
class FruitBox<T extends Fruit> { ... }
지네릭스 예약
- static 멤버는 지네릭스를 적용할 수 없다.
→ 모든 인스턴스에 대해 동일하게 동작해야 하기 때문
- 지네릭스 타입의 배열 생성 불가
→ 연산자는 컴파일 시점에 타입 T가 뭔지 정확히 알아야 하기 때문
- instanceof 연산자도 마찬가지
T[] itemArr = new T[itemArr.length]; // 지네릭 타입 배열 생성불가
와일드 카드
- 지네릭 타입에 다형성을 적용하려면 [와일드 카드]를 사용하면 된다.
<? extends T> - 와일드 카드 상한 제한, T와 그 자식들만 가능
<? super T> - 와일드 카드 하한 제한, T와 그 부모들만 가능
<?> - 제한 없음, 모든 타입 가능 < ? extends Object > 와 동일
T extends Product VS ? extends Product
[T extends Product]
- 클래스나 메서드 정의 시 특정 타입을 고정하기 위해 사용
- 즉, 정의할 때, 타입 제한
class Box<T extends Product> {
private T item;
public void setItem(T item){ this.item = item; }
}
[? extends Product]
- 메서드 호출 시 혹은 컬렉션 제네릭 타입을 지정할 때 사용
- 타입을 고정하지 않고, 특정 범위 내에서 유연하게 처리할 수 있도록 함
- 데이터를 읽기만 하는 범용적인 메서드에서 사용 됨
ArrayList<? extends Product> list = new ArrayList<>();
기타
- ArrayList<T extends Object>가 불가능한 이유
→ 컴파일 시점에 타입 T가 명확하지 않기 때문
- ArrayList<? extends Object> 가 가능한 이유
→ 컴파일 시점에 범위가 명확하기 때문에 가능
- 와일드카드가 쓰기가 불가능한 이유
→ 컴파일 시점에 범위는 명확하지만 정확히 어떤 타입인지 명확하지 않기 때문
지네릭 메서드
- 메서드의 선언부에 지네릭 타입이 선언된 메서드를 지네릭 메서드라고 한다.
- 메서드 정의시 해당 메서드에서 사용할 타입의 범위를 지정하는 것
static <T> void method() { ... };
- 지네릭 클래스의 타입 변수와 지네릭 메서드의 타입변수가 같은 문자여도 서로 다른 것
class Main<T> {
static <T> void main() { }; // 두 타입 변수 T는 다름
}
public <U extends Parent> void test(List<? extends U> list) { ... };
지네릭 타입 형변환
- 지네릭 타입과 원시 타입 간 형변환 가능
단, 경고 발생
- 서로 다른 지네릭 타입 간의 형반환 불가능
Hello<Integer> h1 = new Hello<>();
Hello<Object> h2 = h1; // 불가능
- <? extends T>의 타입 참조변수로 형변환은
T 타입 혹은 그의 자손으로 제니릭 타입을 가지면 가능
→ 컴파일 시점에 정확한 타입이 뭔지는 모르지만 범위가 정해져있기 때문
Hello<Integer> h1 = new Hello<>();
Hello<? extends Number> h2 = h1;
지네릭 타입 제거
- 컴파일러는 지네릭 타입을 이용하여 소스파일을 체크하고
필요한 곳에 형변환을 넣어준다.
- 그리고 소스파일에소 지네릭 타입을 제거한다.
- 즉, 컴파일된 파일에는 지네릭 타입에 대한 정보가 없다.