1.1 지네릭스란?
지네릭스는 다양한 타입의 객체들을 다루는 메서드나 컬레션 클래스의 컴파일 시의 타입 체크(compile - time type check)를 해주는 기능이다.
객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움이 줄어든다.(컴파일 한계를 넘어서게 해줌)
*컴파일러에게 타입정보를 주는 것. 형변환 에러를 줄일 수 있다.
지네릭스의 장점
- 타입 안정성을 제공한다
- 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해진다.
간단히 얘기하면 다룰 객체의 타입을 미리 명시해줌으로써 번거로운 형변환을 줄여준다는 얘기다.
1.2 지네릭 클래스의 선언
지네릭 타입은 클래스와 메서드에 선언할 수 있다.
클래스에 선언하는 지네릭 타입
//일반 클래스
class Box {
Object item;
void setItem(Object item) {this.item = itme;}
Object getItem() {return item;}
}
//지네릭 클래스로 변경
class Box<T> { //지네릭 타입 T를 선언 //Box클래스는 지네릭 클래스가 됐다.
T item;
void setItem(T item) {this.item = item;}
T getItem() {return item;}
}
Box<T>에서 T를 '타입 변수(type variable)'라고 하며, 'Type'의 첫 글자에서 따온 것이다. 타입 변수는 T가 아닌 다른 것을 사용해도 된다. 타입 변수가 여러 개인 경우 Map<K, V>와 같이 콤마','를 구분자로 나열하면 된다.(Key, Value)
이들은 기호의 종류만 다를 뿐 '임의의 참조형 타입'을 의미한다는 것은 모두 같다.
이제 지네릭 클래스가 된 Box클래스의 객체를 생성할 때는 다음과 같이 참조변수와 생성자 타입 T대신에 사용될 실제 타입을 지정해주어야 한다.
Box<String> b = new Box<String>(); //타입 T 대신, 실제 타입을 지정
b.setItem(new Object()); //에러. String이외의 타입은 지정불가
b.setItem("ABC"); //Ok. String타입이므로 가능
String item = (String) b.getItem(); //형변환이 필요없음(String밖에 못들어 온다는걸 알고있음)
위의 코드에서 타입 T대신에 String타입을 지정해줬으므로, 지네릭 클래스 Box<T>는 다음과 같이 정의된 것이다.
class Box { //지네릭 타입을 String으로 지정
String item;
void setItem(String item) { this.item = item; }
String getItem() { return item; }
}
지네릭이 도입되기 이전의 코드와 호환을 위해, 지네릭 클래스인데도 예전의 방식으로 객체를 생성하는 것이 허용된다.(다만, 경고발생)
Box b = new Box(); //OK. T는 Object로 간주된다.
b.setItem("ABC"); //경고. unchecked or unsafe operation
b.setItem(new Ojbect()); //경고. unchecked or unsafe operation
아래와 같이 타입 변수 T에 Object타입을 지정하면, 타입을 지정하지 않은 것이 아니라 알고 적은 것이므로 경고는 발생하지 않는다.
Box<Object> b = new Box<Obejct>();
b.setItem("ABC"); //경고발생 안함
b.setItem(new Ojbect()); //경고발생 안함
앞으로 지네릭 클래스를 사용할 때는 반드시 타입을 지정해서 지네릭스와 관련된 경고가 나오지 않도록 하자.
지네릭스의 용어
Box<T> : 지네릭스 클래스. ‘T의 Box’또는 ‘T Box’라고 읽는다.
T : Box<T>의 타입 변수 또는 타입 매개변수.(T는 타입 문자)
Box : 원시 타입(raw type)
지네릭스의 제한
지네릭 클래스 Box의 객체를 생성할 때, 객체별로 다른 타입을 지정하는 것은 적절하다. 지네릭스는 이처럼 인스턴스별로 다르게 동작하도록 하려고 만든 기능이니까.
* 타입 변수에 대입은 인스턴스별로 다르게 가능
Box<Apple> appleBox = new Box<Apple>(); //OK. Apple객체만 저장가능
Box<Grape> grapeBox = new Box<Grape>(); //OK. Grape객체만 저장가능
①static(모든 인스턴스에 공통) 멤버에 타입변수 사용 불가
class Box<T> {
static T item; //에러
static int compare(T t1, T t2) { ... } //에러
...
}
②배열(객체) 생성할 때 타입 변수 사용 불가. 타입 변수로 배열 선언은 가능
class Box<T> {
T[] itemArr; //OK. T타입의 배열을 위한 참조변수
...
T[] tmpArr() {
T[] tmpArr = new T[itemArr.length]; //에러. 지네릭 배열 생성불가
...
return tmpArr;
}
...
}
지네릭 배열을 생성할 수 없는 것은 new연산자 때문인데, 이 연산자는 컴파일 시점에 타입 T가 뭔지 정확히 알아야 한다. 그런데 위의 코드에 정의된 Box<T>클래스를 컴파일하는 시점에는 T가 어떤 타입이 될지 전혀 알 수 없다. instanceof연산자도 new연산자와 같은 이유로 T를 피연산자로 사용할 수없다.
1.3 지네릭 클래스의 객체 생성과 사용
지네릭 클래스 Box<T>의 객체에는 한 가지 종류, 즉 T타입의 객체만 저장할 수 있다. 전과 달리 ArrayList를 이용해서 여러 객체를 저장할 수 있도록 하였다.
class Box<t> {
ArrayList<T> list = new ArrayList<T>();
void add(T item) {list.add(item);}
T get(int i) { return list.get(i);}
ArrayList<T> getList() { return list;}
int size() { return list.size();}
public String toString() { return list.toString();
}
1.4 제한된 지네릭 클래스
지네릭 타입에 'extends'를 사용하면, 특정 타입의 자손들만 대입할 수 있게 제한할 수 있다.
다음과 같이 지네릭 타입에 ‘extends Fruit’를 사용하면, 특정 타입의 자손들만 대입할 수 있게 제한할 수 있다.
class FruitBox<T Extends Fruit> { //Fruit의 자손만 타입으로 지정가능
ArrayList<T> list = new ArrayList<T>();
...
}
//여전히 한 종류의 타입만 담을 수 있지만,
//Fruit클래스의 자손들만 담을 수 있다는 제한이 더 추가된 것이다.
1.5 와일드 카드
*하나의 참조 변수로 대입된 타입이 다른 객체를 참조 가능
와일드 카드는 기호 '?'로 표현하는데, 와일드 카드는 어떠한 타입도 될 수 있다.
'?'만으로는 Object 타입과 다를 게 없으므로, 다음과 같이 'extends'와 'super'로 상한과 하한을 제한할 수 있다.
<? extends T> : 와일드 카드의 상한 제한. T와 그 자손들만 가능 *많이사용
<? super T> : 와일드 카드의 하한 제한. T와 그 조상들만 가능
<?> : 제한 없음. 모든 타입이 가능. <? extends Obejct>와 동일
1.6 지네릭 메서드
메서드의 선언부에 지네릭 타입이 선언된 메서드를 지네릭 메서드라 한다.
지네릭 클래스에 정의된 타입 매개변수와 지네릭 메서드에 정의된 타입 매개변수는 전혀 별개의 것이다.
1.7 지네릭 타입의 형변환
*지네릭 타입과 원시 타입간의 형변환은 바람직하지 않다.(경고 발생)
Box box = null;
Box<Object> objBox = null;
box = (Box)objBox; //Ok. 지네릭 타입 → 원시 타입. 경고 발생
objBox = (Box<Object>)box; //Ok. 원시 타입 → 지네릭 타입. 경고 발생
1.8 지네릭 타입의 제거
컴파일러는 지네릭 타입을 이용해서 소스파일을 체크하고, 필요한 곳에 형변환을 넣어준다. 그리고 지네릭 타입을 제거한다. 즉, 컴파일된 파일(*.class)에는 지네릭 타입에 대한 정보가 없는 것이다.
이렇게 하는 주된 이유는 지네릭이 도입되기 이전의 소스 코드와의 호완성을 유지하기 위해서이다.
출처 : 남궁성. 「자바의 정석」. 도우출판. 2016
'프로그래밍언어 > Java' 카테고리의 다른 글
[자바의 정석] 12. 애너테이션(annotation) (0) | 2023.09.30 |
---|---|
[자바의 정석] 12. 열거형(enums) (0) | 2023.09.29 |
[자바의 정석] 11. 컬렉션 프레임웍 - Properties / Collection (0) | 2023.08.07 |
[자바의 정석] 11. 컬렉션 프레임웍 - HashMap과 Hashtable / TreeMap (0) | 2023.08.07 |
[자바의 정석] 11. 컬렉션 프레임웍 - HashSet / TreeSet (0) | 2023.08.06 |