제네릭의 정의
- 클래스나 메소드에서 사용할 내부 데이터 타입을 외부에서 지정하는 기법
1
2
3
4
5
6
7
8
9
10
class FruitBox<T> {
List<T> fruits = new ArrayList<>();
public void add(T fruit) {
fruits.add(fruit);
}
}
// main
FruitBox<Apple> appleBox = new FruitBox<>();
제네릭 클래스
- 클래스 선언에 타입 매개변수가 쓰이면, 이를 제네릭 클래스라 한다.
1
2
3
class FruitBox<T> {
}
제네릭을 사용하는 이유
- 제네릭을 사용하는 이유는 두 가지가 있다.
- 1) 컴파일 타임에 자료형의 오류에 대한 검증을 수행하여 런타임시 자료형에 안전한 코드를 실행한다.
- 2) 반환값에 대한 타입 변환 및 타입 검사에 들어가는 노력을 줄일 수 있고, 형변환이 없어지므로 가독성이 좋아진다.
1) 타입 안정성(타입에 대한 검증을 컴파일시 수행하여 타입 안정성을 가진다)
- 위와 같이 제네릭을 쓰지 않으면 문법적으론 문제가 없지만 자료형의 오류에 대한 검증이 컴파일타임에 이루어지지 않음으로써 런타임 도중 발생하게 된다.
자바 컴파일러는 제네릭 코드에 강한 타입 체크를 한다. 타입 안정성에 위배가 된다면 컴파일에러를 발생시킨다.
2)캐스팅 삭제(컴파일하는 타입에 미리 타입이 정해지므로 타입캐스팅을 해줄 필요가 없다.)
- Object클래스는 사용하는 타입으로 형변환을 해줘야하는데 제네릭을 사용하면 컴파일시 미리 타입이 정해지므로 할 필요가 없다.
제네릭 메소드
제네릭 메소드의 형태
- <>안의 타입으로 매개변수의 데이터 타입을 지정
- 타입 파라미터의 범위는 메소드 블록 이내로 한정
1
2
3
public <T> void add(T t) {
// ...
}
제네릭 클래스가 아니더라도 정의할 수 있다.
1
2
3
4
5
class Name {
public <T> void printClassName(T t) {
System.out.println(t.getClass().getName());
}
}
제네릭 클래스와 제네릭 메소드의 타입 매개변수가 같다면 제네릭 메소드의 타입 매개변수를 우선시 한다.
타입 매개변수의 제한
- 만약 T에 Vegetable클래스가 들어가면 잘못된 타입이 들어갈 수 있기에 컴파일에러가 발생한다.
- 그러므로 Fruit에 하위클래스만 들어올 수 있도록 경계를 설정해주어야 한다.
상한 경계
- T extends Fruit
- 타입 매개변수의 클래스는 Fruit클래스이거나 하위클래스이어야 한다.
- 타입 매개변수의 클래스는 Fruit클래스이거나 하위클래스이어야 한다.
- 다음과 같이 Fruit혹은 Fruit의 하위클래스만이 올 것을 보장하면 컴파일에러는 발생하지 않을 것이다.
하한 경계
- T super Fruit
- 타입 매개변수의 클래스는 Fruit클래스거나 Fruit의 상위클래스여야한다.
- 타입 매개변수의 클래스는 Fruit클래스거나 Fruit의 상위클래스여야한다.
와일드카드
비경계 와일드카드(Unbounded Wildcards)
- ?의 형태로 사용. 예를 들어, List<?>
모든 타입이 인자가 될 수 잇다.
비경계 와일드카드의 사용 예시
- List가 어떤 타입이든 출력하는 코드이다.
- 하지만 String은 Object의 인스턴스가 아니기에 컴파일에러가 발생할 것이다.
임의의 타입 A의 리스트 List
<A>
는 List<Object>
의 서브 타입이 아니다.
어떤 타입의 List든 출력하려면 와일드카드(?)를 사용하면 된다.
- 임의의 타입 A의 리스트 List는 List<?>의 서브 타입이다.
비경계 와일드카드의 특징
- 1)List<?>에서 Get한 원소는 Object 타입이다.
- 비경계 와일드카드의 원소는 어떤 타입도 될 수 있다.
- 어떤 타입이 와도 읽을 수 있도록, 모든 타입의 공통 조상인 Object로 받는다.
1 2 3 4
public static void get(List<?> list) { Object object = list.get(0); Integer object = list.get(0); // Compile Error }
- 2)List<?>에는 null만 삽입할 수 있다
- 비경계 와일드카드의 원소가 어떤 타입인지 알 수 없다. 그러므로 타입안정성을 지키기 위해 null만 삽입할 수 있다.
1 2 3 4 5 6 7 8
public static void main(String[] args) { List<Integer> ints = new ArrayList<>(); addDouble(ints); } private static void addDouble(List<?> ints) { ints.add(3.14); // 만약 값을 추가할 수 있다면, List<Integer>에 Double을 추가하는 모순 발생 }
상한 경계 와일드카드(Upper Bounded Wildcards)
- ? extends T(T 상한 와일드 카드)의 형태로 사용. 에를 들어, List<? extends T>(T 상한 와일드 카드의 리스트)
- T혹은 T의 하위 클래스만 인자로 올 수 있다
상한 경계 와일드카드의 특징
- 1)List<? extends T>에서 Get한 원소는 T이다
- 상한 경계 와일드카드의 원소는 T혹은 T의 하위클래스이다.
- 원소들의 최고 공통 조상인 T로 읽으면, 어떤 타입이 오든 T로 읽을 수 잇다
1 2 3 4 5 6
public static void printList(List<? extends Fruit> fruits) { for(Fruit fruit:fruits) { System.out.println(fruit + " "); } System.out.println(); }
- 다음과 같이 Fruit의 하위클래스인 Apple이오면 컴파일에러가 발생할 것이다.
1 2 3 4 5 6
public static void printList(List<? extends Fruit> fruits) { for(Apple fruit:fruits) { System.out.println(fruit + " "); } System.out.println(); }
- 2)List<? extends T>에는 null만 삽입할 수 있다.
- 상한 경계 와일드카드의 원소가 어떤 타입인지 알 수 없다
1 2 3
List<Apple> apples = new ArrayList<>(); List<? extends Fruit> fruits = apples; fruits.add(new Banana()); // Compile Error, List<Apple>에 바나나가 들어갈 수 없다
- 상한 경계 와일드카드의 원소가 어떤 타입인지 알 수 없다
하한 경계 와일드 카드(Lower Bounded Wildcards)
- ? super T(T 하한 와일드카드)의 형태로 사용. 예를 들어, List<? super T>(T 하한 와일드카드의 리스트)
- T 혹은 T의 상위 클래스만 인자로 올 수 있다.
하한 경계 와일드 카드의 특징
- 1)List<? super T>에서 Get한 원소는 Object이다.
- T 하한 경계 와일드카드의 원소는 T의 상위 클래스 중 어떤 타입도 될 수 있다.
- 어떤 타입이 와도 읽을 수 있도록, T들의 공통 조상인 Object로 받는다.
1 2 3 4 5 6
public static void printList(List<? super Fruit> fruits) { for(Object fruit:fruits) { System.out.println(fruit + " "); } System.out.println(); }
- List
<Fruit>
, List<Food>
, List<Object>
가 매개변수로 오면 Object 타입으로 모두 읽을 수 있다.
- 2)List<? super T>에는 T 혹은 T의 하위 클래스만 삽입할 수 있다
- 하한 경계 와일드카드의 원소는 T 혹은 T의 상위클래스이다.
1 2 3 4
List<? super Fruit> fruits = new ArrayList<>(); fruits.add(new Apple()); fruits.add(new Fruit()); fruits.add(new Food()); // Compile Error
- 컴파일 에러의 원인
- 컴파일러는 fruits가 List
<Fruit>
일 경우 Food는 Fruit의 상위 클래스이므로 원소를 추가할 수 없기 때문이다.
- 컴파일러는 fruits가 List
- T 혹은 T의 하위클래스만 삽입할 수 있는 이유
- List
<Fruit>
, List<Food>
, List<Object>
중 어떤 리스트가 올지 fruits는 모른다. 하지만 그 중 어떤 리스트여도, Fruit혹은 Fruit의 하위클래스를 원소로 추가할 수 있다.
- List
- 하한 경계 와일드카드의 원소는 T 혹은 T의 상위클래스이다.