1. 지네릭스(Generics)란?
- 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입체크(compile-time type check)를 해주는 기능
- JDK1.5부터 도입
- 객체의 타입 안전성을 높이고 형변환의 번거로움을 줄임
(객체의 타입 안정성을 높이다 = 의도하지 않은 타입의 객체가 저장되는 것을 막고, 저장된 객체를 꺼내올 때 원래의 타입과 다른 타입으로 잘못 형변환되어 발생할 수 있는 오류를 줄여줌)
2. 지네릭스 클래스의 선언
1) 지네릭 클래스
class Box {
Object item;
void setItem(Object item) { this.item = item; }
Object getItem() { return item; }
}
class Box<T> {
T item;
void setItem(T item) { this.item = item; }
T getItem() { return item; }
}
- Box 클래스를 지네릭 클래스로 변경함
- T : 타입 변수(type variable), 임의의 참조형 타입
- Object타입 대신 원하는 타입을 지정해주면 됨
Box<String> b = new Box<String>();
b.setItem(new Object()); // Error
b.setItem("ABC");
String item = b.getItem(); // 형변환 필요없음
2) 지네릭스의 용어
- Box<T> : 지네릭 클래스. 'T의 BOX' 또는 'T BOX'라고 읽음
- T : 타입 변수 또는 타입 매개변수 (T는 타입 문자)
- Box : 원시 타입(raw type)
- 지네릭 타입 호출 : 타입 매개변수에 타입을 지정하는 것
- 매개변수화된 타입(parameterized type) : 지정된 타입
3) 지네릭스의 제한
① static멤버에 타입 변수 T 사용 불가
- static멤버는 모든 객체에 대해 동일하게 동작해야 하므로 타입 변수 T(인스턴스 변수로 간주) 사용 불가
class Box<T> {
static T item; // Error
static int compare(T t1, T t2) {...} //Error
}
② 지네릭 타입의 배열 생성 불가
- new연산자는 컴파일 시점에 타입 T가 뭔지 정확히 알아야 함
- 지네릭 배열 타입의 참조변수 선언 (O)
- 지네릭 배열 생성 (X)
class Box<T> {
T[] itemArr; // 가능
T[] toArray() {
T[] tmpArr = new T[itemArr.length]; // Error, 지네릭 배열 생성 불가
...
return tmpArr;
}
}
3. 지네릭 클래스 객체 생성과 사용
- 참조변수와 생성자에 대입된 타입(매개변수화된 타입)이 일치해야 함
(Box 객체에는 T 타입의 객체만 저장할 수 있음)
- 일치하지 않으면 에러 발생
Box<Apple> appleBox = new Box<Apple>(); // O
Box<Apple> appleBox = new Box<Grape>(); // Error
- 상속관계에 있어도 X
Box<Fruit> appleBox = new Box<Apple>(); // Error! Apple이 Fruit의 자손이여도 안됨
- 두 지네릭 클래스의 타입이 상속관계에 있고, 대입된 타입이 같은건 O
Box<Apple> appleBox = new FruitBox<Apple>(); // O
// Box와 FruitBox는 상속관계
// 대입된 타입(Apple)이 같음
4. 제한된 지네릭 클래스
- 특정 타입의 자손들만 대입할 수 있게 제약 -> extends
class FruitBox<T extends Fruit> {
ArrayList<T> list = new ArrayList<T>();
...
}
- 특정 인터페이스를 구현한 것만 대입할 수 있게 제약 -> extends (implements가 아님!)
interface Eatable {}
class FruitBox<T extends Eatable> { ... }
- 상속과 인터페이스 둘 다 제약을 걸 수 있음 -> &로 연결
class FruitBox<T extends Fruit & Eatable> { ... }
5. 와일드 카드
- 지네릭 타입이 다른 것만으로는 오버로딩이 성립되지 않음 -> 와일드 카드로 해결
<? extends T> 와일드 카드의 상한 제한. T와 그 자손들만 가능
<? super T> 와일드 카드의 하한 제한. T와 그 조상들만 가능
<?> 제한 없음. 모든 타입이 가능
※ Comparator<? super T>
- Comparator에는 항상 <? super T>를 씀
- 와일드카드를 사용하여 하한 제한을 하지않는 경우, 매 타입마다 Comparator를 구현해줘야 함
- 하한 제한을 함으로써 T와 그 조상들이 공통적으로 사용할 수 있는 Comparator 제공
ex) Collections.sort()
static <T> void sort(List<T> list, Comparator<? super T> c)
6. 지네릭 메서드
- 메서드의 선언부에 지네릭 타입이 선언된 메서드
- 지네릭 타입의 선언 위치는 반환타입 바로 앞
class Jucier {
static <T extends Fruit> Juice makeJuice(FruitBox<T> box) { ... }
}
- 지네릭 메서드를 호출할 때는 타입 변수에 타입을 대입해야 함
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
System.out.println(Juicer.<Fruit>makeJuice(fruitBox));
System.out.println(Juicer.makeJuice(fruitBox)); // 생략 가능, 컴파일러가 타입 추정
System.out.println(<Fruit>makeJuice(fruitBox)); // 생략 불가능, 클래스 이름 생략불가
System.out.println(this.<Fruit>makeJuice(fruitBox)); // 생략 가능
- 매개변수 타입이 복잡할 때도 타입을 별도로 선언함으로써 코드를 간략히 할 수 있음
public static void printAll(ArrayList<? extends Product> list,
ArrayList<? extends Product> list2) { ... }
public static <T extends Product> void printAll(ArrayList<T> list, ArrayList<T> list2) { ... }
7. 지네릭 타입의 형변환
① 지네릭 타입 - 넌지네릭 타입 : 가능하지만 경고
② 대입된 타입이 다른 지네릭 타입 간 : 불가능
③ String -> ? extends Object : 가능
④ ? extends Object -> String : 가능하지만 경고
8. 지네릭 타입의 제거
- 컴파일된 파일(*.class)에는 지네릭 타입에 대한 정보가 없음
-> 지네릭이 도입되기 이전의 소스 코드와의 호환성을 유지하기 위함
참고 - Java의 정석 3rd Edition (저자 : 남궁성, 출판 : 도우출판)
'공부 > Java' 카테고리의 다른 글
[Java-12] 애너테이션(Annotation) (0) | 2021.07.02 |
---|---|
[Java-12] 열거형(Enums) (0) | 2021.07.02 |
[Java] BufferedReader와 BufferedWriter (0) | 2021.06.13 |
[Java-11] 컬렉션 프레임웍 - HashMap, TreeMap, Properties, Collections (0) | 2021.06.07 |
[Java-11] 컬렉션 프레임웍 - Arrays, Comparator, Comparable, HashSet, TreeSet (0) | 2021.04.15 |