컴파일시 타입을 체크해 주는 기능(compile-time type check)
객체의 타입 안정성을 높이고 형변환의 번거로움을 줄여줌// Tv객체만 저장할 수 있는 ArrayList를 생성 ArrayList<Tv> tvList = new ArrayList<Tv>(); tvList.add(new Tv()); // OK tvList.add(new Audio()); // 컴파일 에러. Tv 외의 다른 타입은 저장 불가
장점
- 타입 안정성을 제공한다.
- 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해 진다.
- 컴파일 타임에서 미리 오류를 발생시켜 수정할 수 있게 해준다.
클래스를 작성할 때, Object타입 대신 타입 변수(E)를 선언해서 사용
일반 클래스가 지네릭클래스로 바뀜. 타입 변수는 E를 많이 쓰지만 아무거나 써도 상관없음.
객체를 생성시, 타입 변수(E) 대신 실제 타입(Tv)을 지정(대입)
// 타입 변수 E 대신에 실제 타입 Tv를 대입 ArrayList<Tv> tvList = new ArrayList<Tv>();
ArrayList<Tv> tvList = new ArrayList<Tv>();
tvList.add(new Tv());
Tv t = tvList.get(0); // 형변환 불필요 반환타입이 Tv이기 때문
Box<T> 지네릭 클래스. 'T의 Box' 또는 'T Box'라고 읽는다.
T 타입 변수 또는 타입 매개변수.(T는 타입문자)
Box 원시 타입
참조 변수와 생성자의 대입된 타입은 일치해야 한다.
ArrayList<Tv> list = new ArrayList<Tv>(); // OK. 일치 ArrayList<Product> list = new ArrayList<Tv>(); // 에러. 불일치
지네릭 클래스간의 다형성은 성립.(여전히 대입된 타입은 일치해야)
List<Tv> list = new ArrayList<Tv>(); // OK. 다형성. ArrayList가 List를 구현 List<Tv> list = new LinkedList<Tv>(); // OK. 다형성. LinkedList가 List를 구현
매개변수의 다형성도 성립
List<Tv> list = new ArrayList<Tv>(); list.add(new Product()); list.add(new Tv()); // OK. list.add(new Audio()); // OK.
클래스를 작성할 때, Object타입 대신 T와 같은 타입 변수를 사용
Iterator도 타입 변수로 설정이 되었기 때문에 제네릭스를 사용하면 형변환을 할 필요없이 코드작성이 가능하다.
여러개의 타입 변수가 필요한 경우, 콤마(,)를 구분자로 선언
대입하면 아래 그림처럼 바뀌게 된다.
class FruitBox<T extends Fruit> { // Fruit의 자손만 타입으로 지정가능
}
FruitBox<Apple> appleBox = new FruitBox<Apple>(); // OK.
FruitBox<Toy> toyBox = new FruitBox<Toy>(); // 에러. Toy는 Fruit의 자손이 아님
interface Eatable {}
class FruitBox<T extends Eatable> {} // X implements
Box<Apple> appleBox = new Box<Apple>();
Box<Grape> grapeBox = new Box<Grape>();
class Box<T> {
static T item; // 에러
static int compare(T t1, T t2) {...} // 에러
...
왜 못쓸까요. static 멤버는 모든 인스턴스의 공통으로 쓰는건데 지네릭스는 인스턴스 별로 다르게 사용하게 한거에 static을 쓰면 어쩌자는거! 라는 느낌입니다.
class Box<T> {
T[] itemArr; // OK.
...
T[] toArray() {
T[] tmpArr = new T[itemArr.length]; // 에러. 지네릭 배열 생성 불가
...
그니까 new 다음에 T가 오면 안된다는 뜻. new 다음에 어떤 타입이 오는지 확정지어야 하는데 T는 어떤 타입이 올지 모르기 때문.
하나의 참조 변수로 대입된 타입이 다른 객체를 참조 가능
ArrayList<Product> list = new ArrayList<Tv>(); // 에러. ArrayList<? extends Product> list = new ArrayList<Tv>(); // OK ArrayList<? extends Product> list = new ArrayList<Audio>(); // OK
<? extends T>
와일드 카드의 상한 제한. T와 그 자손들만 가능
<? super T>
와일드 카드의 하한 제한. T와 그 조상들만 가능
<?>
제한없음. 모든 타입이 가능.<? extends Object>
와 동일
메서드의 매개변수에 와일드 카드를 사용
static Juice makeJuice(FruitBox<? extends Fruit> box) { } // Fruit과 Fruit 의 자손을 받을 수 있음. ... System.out.println(Juicer.makeJuice(new FruitBox<Fruit>())); System.out.println(Juicer.makeJuice(new FruitBox<Apple>())); // Fruit의 자손인 Apple 사용 가능
static <T> void sort(List<T> list, Comparator<? super T> c)
class FruitBox<T> { // 지네릭 클래스의 타입변수 <T>
...
static <T> void sort(List<T> list, Comparator<? super T> c) {
... // 위에 클래스에 있는 <T>와 다른 타입변수 <T>이다.
}
}
같아도 되고 달라도 되니 그냥 다르게 쓰자. 헷갈릴수도 있으니.
너무 신경쓰지말고 에러날때 그냥 하면됨.
지네릭 타입과 원시 타입 간의 형변환은 바람직하지 않다.(경고 발생)
와일드 카드가 사용된 지네릭 타입으로는 형변환 가능
Box<Object> objBox = (Box<Object>)new Box<String>(); // 에러. 형변환 불가능 Box<? extends Object> wBox = (Box<? extends Object>)new Box<String>(); // OK Box<? extends Object> wBox = new Box<String>(); // 위 문장과 동일 // 매개변수로 FruitBox<Fruit>, FruitBox<Apple>, FruitBox<Grape> 등이 가능 static Juice makeJuice(FruitBox<? exitends Fruit> box) { ... } FruitBox<? extends Fruit> box = new FruitBox<Fruit>(); // OK FruitBox<? extends Fruit> box = new FruitBox<Apple>(); // OK
컴파일러는 지네릭 타입을 제거하고, 필요한 곳에 형변환을 넣는다.
지네릭 타입의 경계(bound)를 제거. JDK 이전 버전과 같이 호환되게 하기 위해 타입을 제거하도록 만듬.
지네릭 타입 제거 후에 타입이 불일치하면, 형변환을 추가
와일드 카드가 포함된 경우, 적절한 타입으로 형변환 추가