제네릭
제네릭이란
JDK 1.5에 도입된 컴파일 시 타입체크를 해주는 기능. 클래스와 메서드에 선언할 수 있다.
장점
객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움을 줄여준다.
용어
1
2
3
class Car<T> {
...
}
위와 같은 클래스가 정의되어 있다고 할 때
- Car<T>
- 제네릭 클래스
- Car
- 원시 타입
- T
- 타입 매개변수
컴파일러가 소스파일을 컴파일하면 타입 매개변수를 대입된 타입으로 변경하고 제네릭 선언을 제거하여 .class 파일에는 제네릭 선언이 없다.
주의사항
static 변수와 static 메서드에는 타입 매개변수를 사용할 수 없다.
1 2 3 4
class Car<T> { static T speed; // 에러 static int speedUp(T t1) { ... } // 에러 }
제네릭 배열 참조변수를 정의하는 것은 가능하지만 new 키워드로 제네릭 배열을 생성할 수는 없다.
1 2 3 4 5 6 7
class Car<T> { T[] arr; // 가능 void temp() { T[] copy = new T[arr.length]; // 에러 ... } }
제네릭 배열을 생성해야할 때는 리플렉션 API를 사용하거나 Object 배열을 생성해서 복사한 후 형변환하는 방법을 사용해야 한다.
ArrayList 클래스의 toArray(T[] a) 메서드를 참고하면 제네릭 배열의 실제 구현을 볼 수 있다.
제네릭 클래스의 객체를 생성하거나 사용할 때 상속에 주의해야 한다.
- Fruit이 부모 / Apple이 자식 일 때
1
Box<Fruit> fruitBox = new Box<Apple>(); // 상속이라도 안된다
- Box는 부모 / AppleBox는 자식 일 때
1
Box<Apple> appleBox = new AppleBox<Apple>(); // 다형성으로 가능
- Fruit이 부모 / Apple이 자식일 때
1 2
Box<Fruit> fruitBox = new Box<>(); fruitBox.add(new Apple()); // 다형성으로 가능
- Fruit이 부모 / Apple이 자식 일 때
타입 제한
타입 매개변수에 대입할 수 있는 타입을 제한할 수 있다.
1
2
3
class Box<T extends Fruit> {
...
}
위와 같이 선언된 제네릭 클래스는 타입 매개변수에 Fruit 과 그 자식 타입만 대입할 수 있다.
1
2
3
class Box<T extends Eatable> {
...
}
이렇게 하면 타입 매개변수에 Eatable 이라는 인터페이스를 구현한 타입만 대입할 수 있다.
인터페이스지만 implements
를 사용하지 않고 여전히 extends 키워드를 사용한다.
여러 인터페이스를 구현한 타입을 타입 매개변수에 대입하게 하려면 아래와 같이 하면 된다.
1 class Box<T extends Eatable & Saleable> { ... }
와일드 카드
타입 매개변수를 사용할 수 없는 일반 클래스에서 제네릭 클래스를 매개변수로 받아서 처리하는 메서드를 정의할 때 사용
1
2
3
class Juicer {
static int makeJuice(Box<? extends Fruit> box) { ... }
}
<? extends T>
- T 와 그 자식 타입으로 와일드카드 제한
<? super T>
- T 와 그 부모 타입으로 와일드카드 제한
<?>
- 와일드카드 타입 제한 없음.
<? extends Object>
와 동일
와일드카드는 타입 제한과 달리
&
기호를 사용할 수 없다.
제네릭 메서드
메서드의 선언부에 제네릭 타입이 선언된 메서드를 제네릭 메서드라고 한다.
제네릭 메서드는 제네릭 클래스가 아닌 클래스에도 정의할 수 있고 static 키워드가 있어도 오류가 발생하지 않는다.
제네릭 클래스에 제네릭 메서드가 정의되어 있지만 제네릭 클래스의 T 와 제네릭 메서드의 T 는 별개의 것이다.
대표적으로 Collections
클래스의 sort 메서드가 있다.
1
static <T> void sort(List<T> list, Comparator<? super T> c) { ... }