자바 지네릭스(Generics)
자바의 지네릭스(Generics)는 컴파일 시점에 타입을 체크해서 안정성을 높여주는 기능이다.
기존에는 Object로 모든 타입을 처리했기 때문에, 저장할 때와 꺼낼 때 **형변환(casting)**이 필수였다.
지네릭스를 사용하면 컴파일러가 타입을 체크해주므로, 형변환이 필요 없어지고 오류 가능성도 줄어든다.
Box box = new Box();
box.setItem(new Apple());
Apple apple = (Apple) box.getItem(); // 형변환 필요
지네릭스를 적용하면 다음처럼 형변환 없이 사용할 수 있다.
Box<Apple> box = new Box<>();
box.setItem(new Apple());
Apple apple = box.getItem(); // 형변환 필요 없음
즉, 지네릭스는 instanceof + 조건문을 간단하게 작성한 것이다.
지네릭 클래스 선언
// 기존 클래스
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; }
}
T는 타입 변수로, Type의 첫 글자를 따온 것이며 임의의 참조형 타입이다.
지네릭스 용어
class Box<T> {}
- Box<T> : 지네릭 클래스
- T : 타입 변수 또는 타입 매개변수
- Box : 원시 타입 (Raw Type)
- 타입 변수에 실제 타입을 지정하는 것을 지네릭 타입 호출, 지정된 타입을 매개변수화된 타입(대입된 타입)이라고 한다.
지네릭스의 제한
타입 매개변수 T는 인스턴스 변수로 간주되기 때문에 다음과 같은 제약이 있다.
- static 필드나 메서드에서는 사용할 수 없다.
- 제네릭 타입의 배열을 직접 생성할 수 없다. (예: new T[10] → 컴파일 에러)
class Box<T> {
static T item; // 에러
T[] arr = new T[10]; // 에러
}
Box<Apple> appleBox = new Box<Apple>(); // Apple 객체만 가능
Box<Grape> appleBox = new Box<Grape>(); // Grape 객체만 가능
지네릭 클래스는 상속과 관계없이 정확히 일치하는 타입만 허용한다.
지네릭 클래스의 객체 생성과 사용
Box<T> 정의
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(); }
}
Box<T>의 객체를 생성할 때는 다음과 같이 해야 한다.
참조변수와 생성자에 대입된 타입이 일치해야 한다. 일치하지 않으면 에러가 발생한다.
Box<Apple> appleBox = new Box<Apple>();
Box<Apple> appleBox = new Box<Grape>(); // 에러. 대입된 타입이 다름
Box<Fruit> appleBox = new Box<Apple>(); // 에러. 대입된 타입이 다름
하지만, 두 지네릭 클래스의 타입이 상속관계에 있고, 대입된 타입이 같은 것은 괜찮다.
Box<Apple> appleBox = new FruitBox<Apple>(); // 다형성
참조변수의 타입으로부터 Box가 Apple타입의 객체만 저장한다는 것을 알 수 있어서 생성자를 반복해서 타입을 지정해주지 않아도 된다.
Box<Apple> appleBox = new Box<Apple>();
Box<Apple> appleBox = new Box<>(); // Apple 생략 가능. 위의 코드와 동일
객체를 추가할 때, 대입된 타입과 다른 타입의 객체는 추가할 수 없다.
그러나 타입 T가 Fruit인 경우 Fruit의 자손들은 이 메서드의 매개변수가 될 수 있다. -> void add(Fruit item) 이 되기 때문
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
fruitBox.add(new Apple()); // OK.
fruitBox.add(new Grape()); // OK. void add(Fruit item)
지네릭 타입 제한 (extends, &)
특정 클래스의 하위 클래스만 허용하고 싶을 때는 extends를 사용한다.
class FruitBox<T extends Fruit> { // Fruit의 자손만 타입으로 지정 가능
ArrayList<T> list = new ArrayList<>();
void add(T item) { list.add(item); }
}
만약, Fruit의 자손이면서 Eatable 인터페이스도 구현해야 한다면 "&"로 연결:
class FruitBox<T extends Fruit & Eatable> {}
→ 클래스는 하나만 상속 가능, 인터페이스는 여러 개 연결 가능
와일드카드 (?)
와일드카드는 어떤 타입이든 될 수 있다는 뜻을 가진다.
FruitBox<? extends Fruit> box1 = new FruitBox<Apple>(); // OK
FruitBox<? extends Fruit> box2 = new FruitBox<Grape>(); // OK
- <? extends T> : T 또는 T의 자손만 허용 (상한 제한)
- <? super T> : T 또는 T의 조상만 허용 (하한 제한)
- <?> : 모든 타입 허용 (? extends Object와 동일)
지네릭 메서드
메서드 단위로 타입을 일반화할 수 있다.
선언 위치는 반환 타입 앞
public <T> void printArray(T[] array) {
for (T item : array) {
System.out.println(item);
}
}
지네릭 클래스에 정의된 타입 매개변수와 지네릭 메서드에 정의된 타입 매개변수는 전혀 별개의 것이다.
class FruitBox<T> {
∙∙∙
static <T> void sort (List<T> list, Comparator<? super T> c) {
∙∙∙ // T는 지역변수
}
}
'개발 새발 > JAVA' 카테고리의 다른 글
[Java] 자바 객체지향 - 추상클래스 VS 인터페이스 (0) | 2025.04.10 |
---|---|
[Java] 자바 객체 지향 - 인터페이스 (0) | 2025.04.10 |
[Java] 자바 객체지향 - 추상클래스 (0) | 2025.04.09 |
[Java] 자바 객체지향 - 클래스 멤버/인스턴스 멤버 (0) | 2025.04.07 |
[Java] 자바 배열(array) (0) | 2025.04.04 |