// 지네릭 적용 전의 코드
class Box {
Object item;
void setItem(Object item) { this.item = item; }
Object getItem() { return item; }
}
위의 예제를 지네릭 클래스로 변경하려면, 클래스 옆에 <T>를 붙이고, 'Object'를 모두 <T>로 바꾸면 된다.
// 지네릭 적용 후의 코드
class Box<T> { // 지네릭 타입 T를 선언
T item;
void setItem(T item) { this.item = item; }
T getItem() { return item; }
}
지네릭스의 도입 이전에는 다양한 종류의 타입을 다루는 메서드의 매개변수나 리턴타입으로 Object 타입의 참조변수를 사용함으로 인해, 형변환이 불가피했다.
지네릭스가 도입 됨으로서, 타입을 지정하여 주기만 하면 형변환을 하는 번거로움을 덜 수 있다.
// 지네릭 클래스인 Box 클래스의 객체 생성 예제
Box<String> b = new Box<String>(); // 타입 T 대신, 실제 타입 지정
b.setItem(new Object()); // 에러. String 이외의 타입은 지정 불가
b.setItem("ABC"); // OK. String 타입이므로 가능
String item = b.getItem(); // 형변환이 필요없다.
💡 지네릭을 사용하지 않고 지네릭 클래스의 객체 생성은 가능하지만, 권장하지 않는다.
class Box<T> {}
위의 예시처럼 지네릭 클래스 Box가 있다.
지네릭 클래스는 크게 보면 지네릭 클래스, 타입 변수, 원시 타입 으로 이루어져 있다.
위의 예시에서 지네릭 클래스, 타입 변수, 원시 타입은 아래와 같다.
💡 지네릭 타입의 배열을 생성하는 것은 허용되지 않는다.
// 지네릭 클래스의 객체 생성 예제
Box<Apple> appleBox = new Box<Apple>(); // OK
Box<Apple> appleBox = new Box<Grape>(); // 에러 -> 타입 불일치
// Fruit과 Apple이 상속관계일 경우의 예제
Box<Fruit> appleBox = new Box<Apple>(); // 에러. 대입된 타입이 다르다.
// 지네릭 클래스의 타입이 상속 관계에 있을때의 예제
Box<Apple> appleBox = new FruitBox<Apple>(); // OK. 다형성
💡 JDK 1.7부터는 추정이 가능한 경우 타입을 생략할 수 있다.
// 지네릭 클래스의 타입 생략 예제
// 타입 생략 전
Box<Apple> appleBox = new Box<Apple>();
// 타입 생략 후
Box<Apple> appleBox = new Box<>();
Box<T>클래스에 void add(T item)이라는 메서드가 있다고 할때, 대입된 타입과 다른 타입의 객체는 추가가 불가능하다.
그러나 예외도 있는데, 대입된 타입의 자손들을 추가가 가능하다.
// 타입 불일치 예제
Box<Apple> appleBox = new Box<Apple>();
applebox.add(new Apple()); // OK
applebox.add(new Grape()); // 에러. Box<Apple>에는 Apple 객체만 추가 가능하다.
// 자손 추가 예제 (Fruit는 Apple의 조상이다)
Box<Fruit> fruitBox = new Box<Fruit>();
fruitBox.add(new Fruit()); // OK.
fruitBox.add(new Apple()); // OK. void add(Fruit item)
지네릭 타입에 'extends' 키워드를 사용해서 특정 타입의 자손들만 대입할 수 있게 제한할 수 있다.
// 제한된 지네릭 클래스 예제
class FruitBox<T extends Fruit> {
ArrayList<T> list = new ArrayList<T>();
....
}
// 모든 과일은 Fruit가 조상이다.
FruitBox<Apple> appleBox = new FruitBox<Apple>(); // OK.
// 에러. Toy는 Fruit의 자손이 아니다.
FruitBox<Toy> toyBox = new FruitBox<Toy>();
💡 특정 인터페이스를 구현해야 한다는 제약도 사용할 수 있다.
interface Eatable {}
// Eatable 인터페이스를 구현한 클래스만 대입 가능하게 제한
class FruitBox<T extends Eatable> {...}
// Eatable 인터페이스를 구현하고, Fruit의 자손인 클래스만 대입 가능하게 제한
class FruitBox<T extends Fruit & Eatable> {...}
💡 인터페이스를 구현해야 하는 제약을 줄때 implements가 아닌 extends
키워드를 사용한다는 것에 주의하고, 조건이 두개 일경우 '&' 기호로
연결해야한다.