제네릭이 뭔데?
제네릭은 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법이다. 라이브러리를 보다 보면 제네릭을 사용한 코드를 많이 볼 수 있다.
1 | public class Box<T> { |
위 코드는 Box라는 클래스를 정의한 것이다. Box 클래스는 제네릭을 사용하여 Box 클래스를 생성할 때 사용할 데이터 타입을 외부에서 지정할 수 있다. Box 클래스를 사용하는 방법은 다음과 같다.
1 | Box<Integer> box = new Box<>(); |
위 코드는 Box 클래스를 사용하여 Integer 타입의 데이터를 저장하고 출력하는 코드이다. Box 클래스를 생성할 때 Box
제네릭을 사용하는 이유
제네릭을 사용하는 이유는 제네릭을 사용하지 않으면 발생하는 문제를 해결하기 위해서이다. 제네릭을 사용하지 않는 코드를 보자.
1 | public class Box { |
제네릭을 사용하지 않으면 Object를 사용하여 모든 타입을 담을 수 있다. 하지만 잘못된 코드를 짰을 때 런타임 오류가 발생할 수 있다.
또한 오류 검출은 빠르게 할수록 좋다. 제네릭을 사용하면 컴파일 시점에 오류를 검출할 수 있기 때문에 오류를 빠르게 확인할 수 있다.
주의할 점
제네릭을 사용할 때 주의할 점이 있다.
제네릭은 컴파일 시점에만 사용되기 때문에 런타임 시점에는 제네릭 타입이 소거된다(단순 Object 타입으로 변환된다). 따라서 런타임에 제네릭 타입의 실제 타입을 알 수 없다. 이러한 이유로 런타임에 동작하는 new, instanceof와 같은 키워드는 제네릭 타입에 사용할 수 없다.
1 | public class Box<T> { |
위에서 ? 와일드카드를 사용했는데 ?는 모든 타입을 의미한다.
헷갈리는 부분이 있을 수 있으니 다음 표를 참고하자.
타입 | 설명 | 특징 | 사용 사례 |
---|---|---|---|
Box<T> |
제네릭 클래스 | - 컴파일 타임에 타입 체크 - 타입 안전성 보장 - 특정 타입으로 제한됨 |
- 특정 타입의 객체만을 처리할 때 - 타입 안전성을 보장해야 할 때 |
Box |
로 타입 (Raw Type) | - 타입 파라미터 없음 - 모든 타입 허용 - 타입 안전성 없음 - 제네릭 정보 소거 |
- 이전 버전과의 호환성 유지 - 타입 안전성이 중요하지 않은 경우 |
Box<?> |
와일드카드 타입 | - 제네릭 타입 파라미터를 유지 - 특정 타입을 알 수 없음 - 읽기 전용 - 타입 안전성 일부 보장 |
- 메서드 파라미터나 반환 타입에서 제네릭 타입의 불특정성을 나타낼 때 - 읽기만 필요할 때 |
Box<Object> |
제네릭 클래스의 특정 타입 | - Object 타입의 객체만 처리 - 컴파일 타임에 타입 체크 - 타입 안전성 보장 |
- 모든 타입의 객체를 처리해야 하지만 타입 안전성을 유지하고자 할 때 |
제네릭 메서드
제네릭 메서드는 제네릭 타입을 메서드의 파라미터나 반환 타입으로 사용하는 메서드를 말한다. 제네릭 메서드를 사용하면 메서드를 호출할 때마다 타입을 지정할 필요가 없다.
1 | public class Box { |
위 코드는 제네릭 메서드를 사용한 예제이다. 제네릭 메서드를 사용할 때는 메서드 이름 앞에
1 | String str = Box.getValue("Hello"); |
와일드카드
와일드카드는 크게 제한된 와일드카드와 비한정적 와일드카드로 나뉜다. 제한된 와일드카드는 특정 타입으로 제한하는 것이고, 비한정적 와일드카드는 모든 타입을 허용하는 것이다.
1 | public class Box<T> { |
위 코드는 비한정적 와일드카드를 사용한 예제이다. Box 클래스의 setBox 메서드는 Box 타입의 객체를 파라미터로 받는다. 이때 Box 클래스의 제네릭 타입을 모르기 때문에 와일드카드를 사용하여 모든 타입을 받을 수 있도록 하였다.
제한된 와일드카드는 두 가지 방법으로 사용할 수 있다. 상위 바운드와 하위 바운드가 있다. 상위 바운드는 특정 타입의 상위 클래스로 제한하는 것이고, 하위 바운드는 특정 타입의 하위 클래스로 제한하는 것이다.
Box클래스랑 Animal, Pet, Dog 클래스를 만들어서 와일드카드를 사용하는 예제이다. 상속 관계는 다음과 같다.
1 | Animal <- Pet <- Dog |
1 | class Box<T> { |
상위 바운드를 사용한 예제이다. 상위 바운드를 사용하면 특정 타입의 상위 클래스로 제한할 수 있다.
또한 상위 바운드는 읽기 전용으로 사용할 수 있다. 즉, get 메서드로 값을 읽어올 수는 있지만 set 메서드로 값을 설정할 수는 없다.
1 | public class AnimalProcessor { |
하위 바운드를 사용한 예제이다. 하위 바운드를 사용하면 특정 타입의 하위 클래스로 제한할 수 있다.
하위 바운드는 상위 바운드와 반대로 쓰기 전용으로 사용할 수 있다. 즉, set 메서드로 값을 설정할 수는 있지만 get 메서드로 값을 읽어올 수는 없다.
1 | public class AnimalProcessor { |
제한자를 &를 사용해서 여러개 사용할 수도 있다. 예를 들어 <? extends Animal & Pet>
와 같이 사용할 수 있다.
마무리
제네릭은 컴파일 시점에 타입을 체크하여 안전하게 프로그래밍할 수 있도록 도와준다. 제네릭을 사용하면 런타임 오류를 줄일 수 있고, 코드의 가독성을 높일 수 있다. 제네릭은 자바에서 많이 사용되는 기법이므로 잘 익혀두는 것이 좋다.