공부/Java

[Java-12] 지네릭스(Generics)

줭♪(´▽`) 2021. 7. 2. 14:03

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 (저자 : 남궁성, 출판 : 도우출판)