Generic
개념
사전적 정의: "일반적인"
동일한 클래스 내부에 타입을 지정하는것이 아닌, 외부에서 사용자에 의해 타입이 지정되는 것
등장 배경
제네릭 등장전, 컬렉션의 요소들을 다루는 메소드들은 타입이 보장되지 않기 때문에 문제가 발생함
위 메소드는 String처럼 다른 타입을 갖는 컬렉션도 호출 가능
다른 타입을 갖는 컬렉션은 컴파일 시점엔 문제가 없다가 런타임 시점에 매소드 호출 시 에러 발생
타입을 지정해 컴파일 시점에 안정성을 보장할 수 있는 방법으로 제네릭 등장
제네릭 미리 보기
<>
를 통해 리스트에 들어갈 수 있는 요소의 타입을 정해줌<>
에 특정 타입이 아닌E
라는 타입이 들어감첫번째 예시와 같이 타입별로 필요한 List를 구현하는 것이 아닌,
E
타입 이용 가능
이처럼 제네릭을 통해 타입결정에 대한 고민을 해결할수 있음
Generic 사용법
표기법
<>
꺾쇠 괄호 키워드 사용 (다이아몬드 연산자)
타입 매개변수 / 타입 변수
꺾쇠 괄호 안에
식별자 기호
를 지정함으로써 파라미터화 함제네릭을 이용한 클래스나 매소드 설계 시 사용
타입 파라미터 생략
jdk 1.7 버전 이후부터, new 생성자 부분의 제네릭 타입을 생략 가능
FruitBox<Apple> intBox = new FruitBox<>();
구체화
<T>
부분에서 실행부에서 타입을 받아옴내부에서 T 타입으로 지정한 멤버들에게 전파하여 타입이 구체적으로 설정됨
타입 매개변수 | 의미 |
---|---|
<T> | Type |
<E> | Element |
<K> | Key |
<V> | Value |
<N> | Number |
클래스 및 인터페이스 선언
제네릭 선언 (클래스, 인터페이스)
외부에서 지정한 타입인 T는 {}블록 안에서 유효하다.
복수 타입 파라미터
타입 지정이 여러개 필요할 경우 여러개 사용 가능
중첩 파라미터
제네릭 클래스의 사용
이렇게 생성된 제네릭 클래스를 사용할때는 구체적인 타입을 명시해 생성하면 된다.
즉, Map의 제네릭 타입 K는 String이 V는 Integer가 된다.
주의점
Generic활용
제네릭 클래스를 활용해보자!
결과를 살펴보면 각 클래스의 선언된 제네릭에 따라 타입이 결정된 것을 확인할수 있다.
실제 ArrayMap 또한 제네릭을 통해 구현하는 것을 확인할수 있다.
배경 지식
변성
타입의 상속 계층 관계에서 서로 다른 타입 간에 어떤 관계가 있는지를 나타태는 지표
공변성(Covariance)과 반공변성(Contravariance)을 합친 개념
공변
S 가 T 의 하위 타입이면, S[] 는 T[] 의 하위 타입
List<S>
는List<T>
의 하위 타입
반공변
S가 T의 하위 타입이면, T[] 는 S[] 의 하위 타입 (공변의 반대)
List<T>
는List<S>
의 하위 타입 (공변의 반대)
무공변 / 불공변
S 와 T 는 서로 관계가 없음
List<S>
와List<T>
는 서로 다른 타입
Java에서는 일반적으로 제네릭 타입에 대해서 공변성 / 반공변성을 지원하지 않는다. 즉, 자바의 제네릭은 무공변의 성질을 지닌다
Generic 특징
특징
컴파일 타임에 타입 검사를 통해 예외 방지
불필요한 캐스팅을 없애 성능 향상
미리 타입을 지정 & 제한해 놓기 때문에 형 변환(Type Casting)의 번거로움을 줄일 수 있음
타입 겁사에 들어가는 메모리 줄임
가독성 향상
제네릭은 공변성과 상하관계가 없음
주의 사항
제네릭 타입의 객체는 생성 불가
new 연산자 뒤에 제네릭 타입 파라미터가 올수 없음
static 멤버에 제네릭 타입 올수 없음
제네릭 배열 선언
기본적으로 제네릭 클래스 자체를 배열로 만들 수는 없음
하지만 제네릭 타입의 배열 선언은 허용
제네릭은 공변성과 상하관계가 없음
제네릭은 무공변으로, 전달받은 타입으로만 서로 캐스팅이 가능함
자바의 캐스팅
제네릭의 캐스팅
따라서 제네릭의 타입 파라미터(
<>
) 끼리 타입이 상속 관계에 놓여도 캐스팅 불가능원시 타입부분은(꺾쇠 괄호 부분을 제외) 공변성 적용
꺾쇠 괄호 안의 실제 타입 매개변수에 대해서는 공변성이 적용이 되지 않음
컬렉션 프레임워크 상속 관계
제네릭의 상속 관계
공변성이 없는 특징때문에 매개변수로 제네릭을 사용할때 외부에서 대입되는 인자의 캐스팅 문제로 애로사항이 발생함. 하지만 캐스팅이 지원되지 않는다면 유연하게 타입을 결정하기 위한 제네릭의 장점이 사라지게 됨.
해당 문제를 해결하기 위한 기능이 와일드 카드
와일드 카드
컴퓨터 프로그래밍에서
?
와 같이 하나 이상의 문자들을 상징하는 특수 문자
와일드카드(?
)
?
)모든 타입을 대신 할 수 있는 타입
정해지지 않은 unknown type
타입에 제한이 없어
비제한 와일드카드
타입이라함요소를 추가하는 연산은 허용하지 않음
unknown type으로 컴파일러가 구체적으로 어떤 타입인지 알 수 없기에 타입 안정성을 보장할 수 없음
한정적 와일드 카드
특정 타입을 기준으로 상한 범위와 하한 범위를 지정함으로써 호출 범위를 확장 또는 제한 가능
상한 경계 와일드 카드
<? extends MyParent>
MyParent와 unknown의 모든 MyParent 자식 클래스들 가능
와일드카드 타입에 extends를 사용해서 와일드카드 타입의 최상위 타입을 정의함으로써 상한 경계를 설정
상한 경계가 지정된 경우, 하위 타입을 특정할 수 없으므로 새로운 원소를 추가하는 것이 불가능
공변
하한 경계 와일드 카드
<? super MyParent>
MyParent와 unknown의 MyParent 부모 타입들 가능
super를 사용해 와일드카드의 최하위 타입을 정의하여 하한 경계 설정
해당 컬렉션에는 MyParent의 자식 타입이라면 원소를 안전하게 컬렉션에 추가 가능
반공변
주의점
K extends T
와? extends T
는 비슷한 구조지만 차이점이 있음'유형 경계를 지정'하는 것은 같으나, 경계가 지정되고 K는 특정 타입으로 지정이 되지만, ?는 타입이 지정되지 않는다는 의미
제한된 Generic의 사용 예시
extend
extend의 경우 상한경계 즉, 자신과 자손타입만 제네릭으로 지정가능하다.
예시를 보면 로 설정되어 있다, Double은 Number를 상속받기 때문에 괜찮지만, String은 아니기 때문에 에러가 나게 된다.
super의 예시
super는 하한 경계 즉, super뒤에 오는 타입이 최하위 타입으로 지정된다.
보통 업캐스팅이 필요할때 사용된다.
복잡한 예제
코테를 풀다보면 PriorityQueue, TreeSet과 같이 정렬이 필요한 클래스에서 Comparator에 대해 한번쯤 본적 있을것이다. 위의 예시와 같은 제네릭으로 구성되어 있는데 한번 분석해보자
우선 E extend Comparable
을 먼저 살펴보자
extend의 경우 Comparable이 상한 경계가 된다는 의미인데, 이는 곧 Comparable의 구현이 필수적이라는 의미이다.(Comparable은 인터페이스이다.)
즉, 다음과 같이 구현이 필수적이다. 하지만 코드를 살펴보면 Comparable의 제네릭에 super를 사용하지 않은것을 볼수 있다.
그렇다면 왜 다른 클래스들에서는 super를 사용할까? 왜냐하면 구현코드가 다른 클래스를 상속받아 더 큰 범주의 클래스와 비교될수 있기 때문이다.
단순히 <E extends Comparable<E>>
한다면 특정 객체와의 비교에서는 괜찮지만 해당 객체와 더 큰 범주의 객체가 비교되는 순간 제대로 정렬이 안되거나 에러가 발생할수 있다. 따라서 제네릭의 super를 통해 위의 에러를 사전에 방지한다.
와일드 카드사용시 주의점
비한정적 와일드 카드 제네릭의 범위를 extend와 super로 제한하는 것이 아닌 <?>만으로 구성하면 안된다!
?는 어떤 타입이든 올수 있지만, 어떤 타입이든 올수 있기 때문에 컴파일러 입장에서 매개변수를 꺼내거나 저장할시 타입을 알지못해 논리적 에러가 발생하게 된다.
<T super 타입> <T super 타입>의 경우 무수히 많은 클래스와 인터페이스가 올수 있기때문에 Object와 다르지 않아 사용하지 않는다.
PECS(Producer-Extends, Consumer-Super) 공식
이펙티브 자바에서 만든 공식
Producer-Extends
컬렉션으로부터 와일드카드 타입의 객체를 꺼내서 생성하면(produce) extends 사용
Consumer-Super
갖고 있는 객체를 컬렉션에 사용(consumer)하여 더하면 super를 사용
제네릭 메서드
개념
메소드의 선언 부에 적은 제네릭으로 리턴 타입, 파라미터의 타입이 정해지는 메소드
메서드의 선언부에
<T>
가 선언된 메서드직접 메서드에
<T>
제네릭을 설정함으로서 동적으로 타입을 받아와 사용할 수 있는 독립적으로 운용 가능한 메서드
제네릭 메서드인 genericMethod()는 파라미터의 타입에 따라 T타입이 결정됨
결과를 확인하면 같은 a인스턴스이지만 genericMethod의 파라미터에 따라 타입이 달라진것을 확인할수 있음
특징
제네릭 클래스와 독립적
형식과 사용이 제너릭 클래스와 똑같지만, 클래스의
<T>
와 제너릭 메소드의<T>
는 다름제네릭 메소드는 그 메소드를 포함하고 있는 클래스가 제네릭인지 아닌지 상관없음
1번: 클래스의 제너릭 타입 T를그대로 사용하는 경우
2번: 클래스의 제너릭 타입 T와 제너릭 메소드 타입 T는 다름
3번: static 메소드가 아닌 일반메소드기 때문에 클래스의 타입과 제너릭 메소드의 타입 같이 사용가능
4번: static 메소드기 때문에 클래스의 제너릭 타입 T를 사용하기 때문에 에러 발생
제네릭 메서드 사용이유
제네릭 매소드는 호출 시에 매게 타입을 지정하기 때문에 Static이 가능함
정적(static) 메서드를 선언할때 필요
static키워드가 붙으면 프로그램 실행시 메모리에 올라감
외부에서 타입을 결정해주는 제네릭의 특성상 인스턴스 생성이 되기 전까지 타입 결정이 되지 않아 에러가 날수 있음
따라서 static의 경우 제네릭 타입을 메서드의 파라미터를 통해 지정하여 에러를 막을수 있다!
Ref
[Java] 제네릭과 와일드카드 타입에 대해 쉽고 완벽하게 이해하기(공변과 불공변, 상한 타입과 하한 타입)
Last updated