컴파일 시, 타입을 체크해주는 기능 : compile-time type check
객체의 타입 안정성 제공. (Class CastException 방지)
타입 체크와, 형변환을 생략할 수 있으므로, 코드가 간결해짐.
런타임 에러, 실행 중 발생 에러를 어떻게 Compile-time error 로 바꿀 수 있을까? 에서 착안하여 만든게 지네릭스임. 지네릭스로, ClassCastException(Runtime error)을 compile-time error로 끌고온것임.
// Tv 객체만 저장할 수 있는 ArrayList 생성
ArrayList<Tv> tvList = new ArrayList<Tv>(); // 타입변수 E 대신에, 실제 타입 Tv 대입
tvList.add(new Tv()); // ok
tvList.add(new Audio()); // 컴파일 에러. Tv 외 다른 타입 저장 불가
// 장점
ArrayList tvList = new ArrayList();
tvList.add(new Tv());
Tv t = (Tv) tvList.get(0); // Tv - Object 간 타입 불일치로, 형변환 필요
// ↓ 아래와 같이 간소화 가능
ArrayList<Tv> tvList = new ArrayList<Tv>();
tvList.add(new Tv());
Tv t = tvList.get(0); // 형변환 불필요 : 반환타입이 Tv 이기 때문. (타입 일치)
지네릭 클래스 작성 시, Object 타입 대신, 타입 변수(E)를 선언하여 사용.
Object를 포함한 클래스는 대부분 지네릭 클래스로 변경되었음.
객체 생성 시, 타입변수 E 대신, 실제 타입을 지정해줘야 함(대입)
타입변수 대신, 실제 타입이 지정되면, 형변환 생략이 가능하다.
Box<T> : 지네릭 클래스. T의 Box 또는, T Box 라고 읽는다. "타입변수 T 선언"
T : 타입 변수 또는 타입 매개변수. (T는 타입 문자)
Box : 원시 타입 (raw type). 일반 클래스일 떄의 타입. 원래 타입.
// 지네릭 클래스 선언
class Box<T> {}
// ↓ 지네릭 타입 호출 ↓ : 타입이 서로 일치해야 함.
Box<String> b = new Box<String>();
// ↑ 대입된 타입 (매개변수화 된 타입) : 생성할 때마다, 다른 타입을 넣을 수 있음.
// ↓ 지네릭 타입 호출 ↓ : 타입이 서로 일치해야 함.
Box<조상> b = new Box<자손>(); // 이렇게는 안됨! 에러남!! 반드시 일치해야 됨.
List<Tv> list = new ArrayList<Tv>(); // OK. 다형성. ArrayList가 List 구현
List<Tv> list = new LinkedList<Tv>(); // OK. 다형성. LinkedList가 List 구현
class Product {}
class Tv extends Product {}
class Audio extends Product {}
ArrayList<Product> list = new ArrayList<Product>();
list.add(new Product());
list.add(new Tv()); // Product의 자손도 add 가능.!
list.add(new Audio());
// add method 상황 설명
boolean add (E e) {...}
->
boolean add(Product e) {...}
// ↑ 다형성에 의해, Product와 그 자손 객체가 오는 것이 가능하다.
// get method도 같다
E get(int index) {...}
->
Product get(int index) {...} // get의 반환 타입이 Product임.
Product p = list.get(0);
Tv t = (Tv) list.get(1); // Product를 반환하므로, Tv로 형변환 필수
public interface Iterator<E> {
boolean hasNext();
E next(); // 원래 iterator는 object를 반환해서, 형변환이 필요했음
void remove();
}
// key value
HashMap<String, Student> map = new HashMap<String, Student>();
map.put("자바왕", new Student("자바왕",1,1,100,100,100));
extends를 이용해서, 대입할 수 있는 타입을 제한할 수 있다.
인터페이스의 경우에도 extends를 사용해야 한다.
// 인터페이스도 타입 제한에서는 extends를 씁니다.
interface Eatable {}
class FruitBox<T extends Eatable> {}
class FruitBox<T extends Fruit> { // Fruit의 자손 type만 대입 가능
ArrayList<T> list = new ArrayList<T>();
...
}
FruitBox<Apple> appleBox = new FruitBox<Apple>(); // OK
FruitBx<Toy> toyBox = new FruitBox<Toy>(); // Error. Toy는 Fruit의 자손이 아님.
타입 변수에 대입은, 인스턴스 별로 다르게 할 수 있다.
Box<Apple> appleBox = new Box<>(); // Apple 객체만 저장 가능
Box<Grape> grapeBox = new Box<>(); // Grape 객체만 저장 가능
Static 멤버에 타입 변수 사용 불가
Static : 모든 인스턴스의 공통이므로, 인스턴스마다 다르게 설정할 수 있는 타입변수를 사용할 수 없다.
객체 혹은 배열 생성 시, 타입 변수 사용 불가. (new 연산자는 뒤에 오는 타입이 "확정"되어있어야한다. T는 확실하지 않기 때문에 사용 불가! = new 연산자 다음에는 T를 사용할 수 없다!)
타입 변수로 배열 선언은 가능.
class Box<T> {
static T item; // 에러
static int compare(T t1, T t2) { ... } // 에러
}
class Box<T> {
T[] itemArr; // OK. T 타입 배열을 위한 참조 변수
...
T[] toArray() {
T[] tmpArr = new T[itemArr.length]; // 에러. 지네릭 배열 생성불가
}
}
하나의 참조 변수로, 대입된 타입이 다른 객체를 참조할 수 있음
< ? extends T > : 와일드 카드의 상한 제한. T와 그 자손들만 가능 -> 주로 이걸 많이 씀!
< ? extends T > : 와일드 카드의 하한 제한. T와 그 조상들만 가능
< ? > : 제한 없음. 모든 타입이 가능. < ? extends Object >와 동일함
메서드의 매개변수에 와일드카드 사용 가능
// 대입된 타입이 일치하지 않아도 ok
// 참조변수 하나로 대입된 타입이 여러개가 될 수 있도록 유연하게 만들어줌
ArrayList<? extends Product> list = new ArrayList<Tv>(); // ok
ArrayList<? extends Product> list = new ArrayList<Audio>(); // ok
ArrayList<Product> list = new ArrayList<Tv>(); // 에러. 대입된 타입 불일치
// ex
static Juice makeJuice(FruitBox<? extends Fruit> box) { ... }
// 둘다 가능
Juicer.makeJuice(new FruitBox<Fruit>()));
Juicer.makeJuice(new FruitBox<Apple>()));
지네릭 타입이 선언 된 메서드 (타입 변수는 메서드 내에서만 유효)
static < T > void sort(List< T > list, Comparator< ? super T > c)
클래스의 타입 매개변수 < T >와 메서드의 타입 매개변수 < T >는 별개이다.
메서드를 호출할 때마다 타입을 대입해야 하나, 대부분 생략 가능하다.
메서드를 호출할 때, 타입을 생략하지 않을 때는 클래스 이름 생략 불가이다. (생략이 불가능 한 것은 드물다!)
class FruitBox<T> { // 지네릭 클래스
...
// 지네릭 메서드
static <T> void sort(List<T> list, Comparator<? super T> c) {
...
// 메서드 내에서는 lv가 더 우선이기 때문에, 메서드의 타입변수가 더 우선이다.
}
// 지네릭 클래스의 타입변수와, 지네릭 메서드의 타입변수는 다른 타입변수이다.
// 그렇기 때문에, 서로 타입이 같아도 되고 달라도 된다.
}
// ex
// Fruit 또는 자손 T에는 Fruit 또는 자손만 대입 가능
// FruitBox<Fruit>, FruitBox<Apple> ok
static <T extends Fruit> Juice makeJuice(FruitBox<T> box) {
String tmp = "";
for(Fruit f : box.getList())
tmp += f + " ";
return new Juice(tmp);
}
// 대부분, 만든 객체와 메서드에 넣는 객체의 타입이 일치하기 때문에, 생략 가능하다.
// 메서드를 호출할 때, 타입을 생략하지 않을 때에는 클래스 이름 생략이 불가능하다.
System.out.println(<Fruit>makeJuice(fruitBox)); // 에러. 클래스 이름 생략 불가
System.out.println(this.<Fruit>makeJuice(fruitBox)); // ok
System.out.println(Juicer.<Fruit>makeJuice(fruitBox)); // ok
// 와일드카드가 사용된 지네릭 메서드 (위 코드와 바꿔서 사용할 수 있다.)
static Juice makeJuice(FruitBox<? extends Fruit> box) {...}
기본적으로, 와일드카드는 "하나의 참조 변수"로 서로 다른 타입이 대입된 여러 지네릭 객체를 다룰수 있게 하기 위한 것이다.
지네릭 메서드는 지네릭 클래스처럼, 호출할 때마다 다른 지네릭 타입을 대입할 수 있게 한 것이다.
와일드카드와 지네릭 메서드는 서로 용도가 다르다!
와일드카드를 쓸 수 없을 때, 지네릭 메서드로 사용하는 경우가 많다.
대부분, 만들기 보다는 사용하는 경우가 많다! 많이 사용을 해보자~~
지네릭 타입과 원시 타입 간의 형변환은 바람직하지 않다. (경고 발생)
서로 다른 타입이 대입 된 지네릭 타입 간의 형변환은 에러가 발생한다.
와일드 카드가 사용된 지네릭 타입으로는 형변환이 가능하다.
Box<Object> objBox = null;
Box box = (Box) objBox; // 지네릭 -> 원시
objBox = (Box<Object>) box; // 원시 -> 지네릭
// 와일드 카드가 사용된 지네릭 타입으로는 형변환ok
Box<? extends Object> wBox = (Box<? extends Object>) new Box<String>();
Box<? extends Object> wBox = new Box<String>();
FruitBox