지네릭스란?
지네릭스란 jdk1.5부터 도입된 개념으로 여러 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 단계에서 타입체크(compile-time type check)를 해주는 기능입니다.
이에 따라 객체의 타입 안정성이 높아지고 형변환의 번거로움이 줄어들게 되었습니다.
지네릭 클래스 선언과 사용
지네릭 타입은 클래스와 메서드에 선언할 수 있습니다. 먼저 클래스에는 아래와 같이 선언된 클래스 Box를 지네릭 클래스로 선언하고 싶으면 클래스 옆에 '<T>'를 붙이고 object를 'T' 로 바꿔주면 선언이 됩니다.
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>에서 T를 타입 변수(type variable) 이라고 합니다. 여기서 T를 다른 문자로 사용해도 상관이 없다. 이제 지네릭 클래스가 된 Box 클래스의 객체를 생성할 때는 다음과 같이 참조변수와 생성자에 타입 T 대신에 사용할 실제 타입을 넣어주면 됩니다.
Box<String> b = new Box<String>; //타입 T 대신, 실제 타입을 지정
b.setItem(new Object()); //경고, 이전 버전의 코드 호환을 위해 허용하지만 타입 불안정 경고(T를 Object로 간주)
b.setItem("ABC") //OK, String 타입이므로 가능
String item = (String) b.getItem();//이 코드처럼 형변환이 필요 없음
코드 호환성을 위해 이전 방식으로도 객체 생성이 가능하지만 unchecked or unsafe operation 경고가 발생합니다. 다만 new Box<Object>(); 처럼 생성하면 경고는 발생하지 않습니다.
또한 static 멤버에 타입 변수 T를 사용할 수 없습니다. 모든 객체에 대해 동일하게 동작해야 하는 static 멤버에 인스턴스변수로 간주되는 T를 참조할 수 없기 때문입니다.
class Box<T>{
static T item; //에러
static int compare(T t1, T t2){...} //에러
}
그리고 지네릭 타입의 배열을 생성하는 것도 허용되지 않습니다.
class Box<T>{
T[] itemArr;
T[] toArray(){
T[] tmpArr = new T[itemArr.length]; //에러
return tmpArr;
}
}
왜냐하면 new 연산자는 컴파일 시점에 타입 T가 뭔지 정확히 알아야하기 때문입니다. 이와 같은 이유로 instanceof 연산자도 사용할 수 없습니다. 그래서 지네릭 배열을 생성해야 할 때는 newInstance()와 같이 동적으로 객체를 생성하는 메서드를 사용하거나 tArr = (E[ ])new Object [ ]와 같이 'T[ ]'로 형변환을 해야 합니다.
Box<T> 객체를 생성할 때는 참조변수와 생성자에 대입된 타입이 같아야 합니다. 또한 상속관계에 있는 지네릭 클래스간의 다형성이 성립하며, 타입 추정이 가능한 경우 타입 생략이 가능합니다.
Box<Apple> appleBox = new Box<Apple>(); //OK
Box<Apple> appleBox = new Box<Grape>(); //에러
Box<Apple> appleBox = new FruitBox<Apple>(); //OK 다형성 성립
Box<Apple> appleBox = new Box<>; //OK 타입 추정이 가능한 경우 생략 가능
제한된 지네릭 클래스
만약 타입의 종류를 제한하려고 한다면 타입 매개변수에 다음과 같이 'extends' 키워드를 사용하면 됩니다.
class FruitBox<T extends Fruit>{
ArrayList<T> list = new ArrayList<T>;
}
또한 클래스가 아닌 인터페이스를 구현하는 제약도 'implements' 가 아닌 'extends'를 사용하며 '&' 기호로 여러 클래스와 인터페이스를 연결하여 다중상속 조건을 걸 수 있습니다.
와일드 카드
어떤 클래스에 static 메서드가 있다고 하면, 타입 매개변수는 인스턴스 변수로 받아들여지기 때문에 지네릭스를 사용할 수 없거나 아래처럼 특정 타입을 지정해 주어야 합니다.
class Juicer {
static Juice makeJuice(FruitBox<Fruit> box) {
...
}
}
이처럼 타입을 지정해 놓으면 해당 타입 이외의 타입은 메서드의 arguments로 올 수 없다는 문제가 생깁니다. 이럴 때 사용하는 것이 바로 와일드 카드입니다.
- <? extends T> 와일드 카드의 상한 제한. T와 그 자손들만 가능
- <? super T> 와일드 카드의 하한 제한. T와 그 조상들만 가능
- <?> 제한 없음. <? extends Object>와 동일
아래처럼 위의 메서드를 와일드 카드를 사용하여 여러 타입을 사용 할 수 있습니다.
static Juice makeJuice(FtruitBox<? extends Fruit> box) {
...
}
지네릭 메서드
메서드의 선언부에 지네릭 타입이 선언된 메서드를 지네릭 메서드라고 합니다.
class FruitBox<T> {
static <T> void sort(List<T> list, Comparator<? super T> c){
...
}
}
위와 같이 지네릭 메서드로 선언하면 static 메서드도 타입 매개변수를 사용할 수 있게 됩니다. 여기서 FruitBox와 sort에 선언된 T는 각각 다른 T 입니다.
앞서 나왔던 makeJuice 메서드를 지네릭 메서드로 바꾸면 다음과 같습니다.
static <T extends Fruit> Juice makeJuice(FruitBox<T> box){
...
}
지네릭 메서드를 사용하면 매개변수 타입이 복잡할 때 유용합니다.
틀린 부분이 있으면 지적해주시면 감사하겠습니다.
'개발 > Java' 카테고리의 다른 글
[Java] 인터페이스(Interface) (0) | 2024.05.15 |
---|---|
[Java] 오토 박싱 & 오토 언박싱 (1) | 2024.02.27 |
[Java] hashCode와 equals 오버라이드(hashCode는 주소값이 아니다.) (1) | 2023.12.16 |
[Java]부동소수점수(2진 체계의 부호 표현법) (0) | 2023.12.16 |