본문 바로가기

Java

지네릭스(Generics)

출처 - Java의 정석 기초편 (남궁 성)

 

 

1. 지네릭스

- 컬렉션 클래스 컴파일시 타입 체크 해주는 기능(compile-time type check)-JDK 1.5

- 컴파일러 한계: 실행시 에러(형변환 에러)→컴파일 에러→지네릭스 도입

- 장점: 타입 안정성 제공, 타입 체크와 형변환 생략 가능

 

// Tv객체만 저장할 수 있는 ArrayList 생성
ArrayList <Tv> tvList = new ArrayList<Tv>(); // 타입 지정
tvList.add(new Tv()); //
tvList.add(new Audio()); // 컴파일 에러, Tv 타입 외 저장 불가

Tv t = tvList.get(0); // 저장된 객체 꺼낼 때 Object가 아닌 Tv 반환 → 형변환 불필요

 

 

2. 타입 변수(type variable)

- 타입 변수 <E> 선언하고 일반 클래스 Object → E 변경

- 타입 변수 여러개면 콤마(,)로 구분: Map<K, V>

 

// 지네릭 클래스 작성시 Object 타입 대신 타임 변수 E 선언
public class ArrayList<E> extends AbstractList<E> { // 일부 생략
    private transient E[] elementData;
    public boolean add(E o) { /* 생략 */ }
    public E get(int index) { /* 생략 */ }
    ...
}

// 객체 생성시 타입 변수 E 대신 실제 타입 대입
ArrayList<Tv> tvList = new ArrayList<Tv>(); // 참조변수, 생성자에 실제 타입 Tv 대입

 

 

3. 지네릭스 용어

 

class Box<T> {}
// 지네릭 클래스: Box<T>
// 타입 변수, 타입 매개변수: T
// 원시 타입(일반 클래스): Box

Box<String> b = new Box<String>();
// 지네릭 타입 호출: Box<String>
// 대입된 타입(parameterized type): String
// 컴파일 후 지네릭 타입 제거되어 원시 타입 Box로 바뀜

 

 

4. 지네릭  타입 다형성

- 참조변수와 생성자에 대입된 타입 일치

- 클래스 타입 간 다형성은 성립(대입된 타입 일치)

- 매개 변수의 다형성도 성립

 

ArrayList<Tv> list = new ArrayList<Tv>(); // ok
ArrayList<Product> list = new ArrayList<Tv>(); // 상속 관계라도 불일치 에러

List<Tv> list = new ArrayList<Tv>(); // ok

ArrayList<Product> list = new ArrayList<Product>();
list.add(new Product());
list add(new Tv()); // ok
list add(new Audio()); // ok
// boolean add(Product e) {...} // Product와 그 자손 객체 가능

Product p = list.get(0); // 형변환 불필요
Tv t = (Tv)list.get(1); // 형변환 필요
// Product get(int index) {...} // 반환타입 Product

 

 

5. Iterator<E>

- 지네릭스 적용

 

public interface Iterator<E> {
    boolean hasNext();
    E next();
    void remove();
}

// E → Student 대입
Iterator<Student> it = list.iterator();
while (it.hasNext()) {
    Student s = it.next(); // Student 반환 → 형변환 불필요
}

 

 

6. HashMap<K, V>

- 타입 변수 여러개면 콤마(,)로 구분(n개 가능)

 

public class HashMap<K, V> extends AbstractMap<K, V> { // 일부 생략
    public V get(Object key) { /* 생략 */ }
    public V put(K key, V value) { /* 생략 */ }
    public V remove(Object key) { /* 생략 */ }
}

HashMap<String, Student> map = new HashMap<String, Student>(); // 생성
map.put("레이", new Student("레이", 1, 1, 100, 100, 100)); // 저장

Student s1 = map.get("1-1"); // 반환타입 Student → 형변환 불필요

 

 

7. 제한된 지네릭 클래스

- T 모든 타입 가능 →  extends로  타입 제한

- 인터페이스도 extends 사용

 

class FruitBox<T extends Fruit> { // Fruit(포함) 자손만 타입 지정 가능
    ArrayList<T> list =  new ArrayList<T>();
}

FruitBox<Apple> appleBox = new FruiitBox<Apple>(); // ok

interface Eatable {}
class FruitBox<T extends Fruit & Eatable> {...} // implements 사용 안함

 

 

8. 지네릭스 제약

- 인스턴스별로 타입 변수 다르게 지정 가능

- static 멤버에 타입 변수 사용 불가(T는 인스턴스 변수 간주)

-  객체, 배열 생성시 타입 변수 사용 불가(new 연산자 다음 T 불가)

 

Box<Apple> appleBox = new Box<Apple>(); // ok
Box<Grape> grapeBox = new Box<Grape>(); // ok

class Box<T> {
    static T item; // 에러
    static int compare(T t1, T t2) {...} // 에러
    T [] tmpArr = new T[itemArr.length]; // 에러
}

 

 

9. 와일드 카드<?>

- 와일드 카드의 상한 → T와 그 자손들: <? extends T>

- 와일드 카드의 하한 → T와 그 조상들: <? super T>

- 제한 없음 → 모든 타입 가능: <?> == <? extends Object>

- 지네릭  타입 다형성: 하나의 참조 변수로 대입된 타입이 다른 객체  참조 가능

- 메서드의 매개 변수에 와일드 카드 사용

ArrayList<? extends Product> list = new ArrayList<Tv>(); // ok
ArrayList<? extends Product> list = new ArrayList<Audio>(); // ok

 

 

10. 지네릭 메서드

- 지네릭 타입이 선언된 메서드(타입 변수는 메서드 내에서만 유효)

- 클래스의 타입 매개변수 <T>와 메서드의 타입 매개변수 <T>는 별개

- 메서드를 호출할 때마다 타입 대입→대부분 생략 가능

 

class FruitBox<T> { // 지네릭 클래스
    static <T> void sort(List<T> list, comparator<? super T> c) { // 지네릭 메서드
        ...
    }
}

 

 

11. 지네릭 타입 형변환

- 지네릭 타입과 원시 타입 간 형변환 가능, 경고 발생

- 대입된 타입이 다른 지네릭 타입 간 형변환 불가능

- 와일드 카드가 사용된 지네릭 타입으로는 형변환 가능

 

// 지네릭 타입 ↔ 원시 타입: 형변환 가능, 경고 발생
box = (Box)objBox;
objBox = (Box<Object>)box;

// 대입된 타입이 다른 지네릭 타입 간: 형변환 불가능
objBox = (Box<Object>)strBox; // 에러
strBox = (Box<Strign>)objBox; // 에러

// 와일드 카드 사용된 지네릭 타입: 형변환 가능 → 생략됨
FruitBox<? extends Fruit> box = new FruitBox<Fruit>;
FruitBox<? extends Fruit> box = new FruitBox<Apple>();

 

 

12. 지네릭 타입 제거

- Java 하위호환성 유지: 컴파일시 <T>→Object 또는 적절한 타입

- 지네릭 타입 제거→타입 불일치시 형변환 추가

 

class Box<T extends Fruit> {
    void add(T t) {...}
}

// 컴파일
class Box {
    void add(Fruit t) {...}
}

 

 

 

'Java' 카테고리의 다른 글

애너테이션  (0) 2022.11.08
열거형(enum)  (0) 2022.11.04
HashMap, TreeMap, Collections  (0) 2022.10.30
HashSet, TreeSet(binary search tree)  (0) 2022.10.29
Arrays, Comparable, Comparator  (0) 2022.10.28