자바의 제네릭에 대해 학습한다.
자바5 버전 이전에는 클래스나 메소드에서 매개변수나 반환값으로 다양한 타입을 사용하는 경우 Object
타입을 사용했다.
하지만 이 경우에는 반환된 Object
객체를 다시 원하는 타입으로 형변환해야하고, 이때 오류가 발생할 가능성도 생긴다.
따라서 자바 5버전부터 제네릭이 도입되어, 다양한 타입의 객체들을 다루는 메소드나 컬렉션 클래스에 컴파일 시의 타입체크를 해준다.
컴파일 시의 타입체크란 다양한 타입이 실제 대입된 타입으로 컴파일시간에 치환된다는 뜻이다.
예를 들어, ArrayList
와 같은 컬렉션 클래스는 다양한 타입의 객체를 담을 수 있긴 하지만, 꺼낼 때 마다 타입체크를 하고 형번환을 하는 것은 불편하다. 또한, 원하지 않은 타입의 객체가 포함되는 것을 막을 방법이 없다. 이러한 문제를 제네릭이 해결해준다.
class GenericSample {
Object element;
void setElement(Object element) {
this.element = element;
}
Object getElement() {
return element;
}
}
위의 코드는 제네릭을 사용하면 아래와 같다.
class GenericSample<T> {
T element;
void setElement(T element) {
this.element = element;
}
T getElement() {
return element;
}
}
T
를 타입 변수, GenericSample<T>
를 제네릭 클래스, GenericSample
를 원시 타입이라고 한다.Map<K, V>
와 같이 ,
를 구분자로 나열하면 된다.타입 변수대신 실제 타입을 지정한다.
GenericSample<String> genericSample = new GenericSample<String>();
GenericSample<String>
은 컴파일 후에 이들의 원시 타입인 GenericSample
로 바뀌고 타입 변수는 모두 Object
으로 치환된다. 그리고 String 타입으로 형변환되는 코드가 추가된다.GenericSample<String> genericSample = new GenericSample<>();
지정해준 타입만 사용할 수 있다.
genericSample.setElement(new Object()); // String이외의 타입 사용불가
genericSample.setElement("HELLO") // String 타입이므로 가능
String s = genericSample.getElement(); // 형변환이 필요없음
제네릭 타입 변수에 extends
를 사용하면, 특정 타입의 자손들만 대입될 수 있게 제한할 수 있다.
class FruitBox<T extends Fruit> {
ArrayList<T> fruitList;
void addFriut(T fruit) {
fruitList.add(fruit);
}
}
FruitBox<Apple> appleBox = new FruitBox<>();
FruitBox<Toy> toyBox = new FruitBox<>(); // ❌
extends
를 사용한다.&
로 연결한다.?
기호로 표현하며, 어떠한 타입도 될 수 있다는 의미이다.
<? extends T>
: T와 그 자손들만 가능
<? super T>
: T와 그 조상들만 가능
<?>
: 제한 없음, <? extends Object>
와 동일
와일드 카드에서는 여러개의 제약사항을 &
로 연결할 수 없다.
메소드의 선언부의 반환 타입 앞에 제네릭 타입이 선언된 메소드를 제네릭 메소드라고 한다.
class FruitBox<T> {
static <T> void sort(List<T> list, Comparator<? super T> c)
}
앞에서 static 멤버에는 타입 변수가 올 수 없다고 했다.
따라서 위의 코드에서 제네릭 클래스 FriutBox에 선언된 타입 변수 T와 제네릭 메소드 sort()
에 선언된 타입 변수 T는 문자만 같을 뿐 서로 다른 것이다.
그렇다면 제네릭 메소드에 선언된 타입 변수는 기존의 타입 변수와는 어떤 의미가 있는것일까?
메소드에 선언된 제네릭 타입은 지역 변수와 같이 매서드 내에서만 지역적으로 사용된다.
제네릭 메소드 사용하기 전
static Juice makeJuice(FruitBox<? extends Fruit> box) {
String tmp = ""
for(Fruit f : box.getList()) tmp += f + " ";
return new Juice(tmp);
}
제네릭 메소드 사용한 후
class Juicer {
static <T extends Fruit> Juice makeJuice(FruitBox<T> box) {
String tmp = ""
for(Fruit f : box.getList()) tmp += f + " ";
return new Juice(tmp);
}
}
FruitBox<Apple> appleBox = new FruitBox<Apple>();
Juicer.<Apple>makeJuice(appleBox);
Juicer.makeJuice(appleBox);
static 메소드도 제네릭 메소드로 만들 수 있다.
Reference
- 자바의 정석 3rd Edition, 남궁성 지음