제네릭 톺아보기2 - 제네릭의 형태구체화 / 비 구체화 타입매개변수화 타입(Parameterized Type)바운디드 타입(Bounded Type)와일드 카드란?언바운드디드 와일드카드(Unbounded WildCard)바운드디드 와일드카드(bounded WildCard) Upper Bounded WildCard (Read)Lower Bounded WildCard (Write)재귀적 타입 바운드인터페이스에 대한 Upper Lower Bounded Wildcard제네릭 메서드Q&A멘토님 피드백
제네릭 톺아보기2 - 제네릭의 형태
구체화 / 비 구체화 타입
- 비 구체화 타입이란?
- 타입 소거자에 의해 컴파일 타임에 정보가 사라지는 것(런타임에 구체화 하지 않는 것)
- E, List<E>, List<String>과 같은 타입들을 비 구체화 타입이라고 합니다.
- 비 구체화 문제는 자바에서 제네릭을 사용하지 않는 버전 즉 하위호환성을 위해서 제공했던 기능 때문에 발생했으며 하위호환성을 위해 객체의 타입 정보를 컴파일 시에 확인하여 런타임 시 동일한 동작을 보장받게 한다.
- 구체화 타입이란?
- 자신의 타입 정보를 런타임 시에 알고 지키게 하는 것(런타임에 구체화 하는 것)
- 런타임 시 완전하게 오브젝트 정보를 표현할 수 있는 타입
- primitives, non-generic type, raw types 등이 있다.

매개변수화 타입(Parameterized Type)
- 하나 이상의 타입 매개변수를 선언하고 있는 클래스나 인터페이스를 제네릭 클래스, 제네릭 인터페이스라고 하며 이를 합쳐서 제네릭 타입 이라고 합니다.
- 각 제네릭 타입에는 매개변수화 타입들을 정의할 수 있습니다.
// Before List<String> list = new ArrayList<>();
- 꺽쇠 괄호 <> 안에 있는 String을 “실 타입 매개변수" 라고 합니다.
- List 인터페이스에 선언되어 있는 List<E>의 E를 형식 타입 매개변수라고 합니다.
- 제네릭은 타입 소거자에 의해 자신의 타입 요소 정보를 컴파일 시 삭제합니다.
// After ArrayList list = new ArrayList();
- 컴파일러는 컴파일 단계에서 List 컬렉션에 String 타입 만! 저장되어야 한다는 것을 명시해 주며 String 타입만을 쓴다는 것을 보장해 주기 때문에 ArrayList로 변경하여도 런타임 시 동일한 동작을 보장받을 수 있게 됩니다.
바운디드 타입(Bounded Type)
- 아래의 치킨박스 예시 코드를 다시 가지고 왔습니다.
- 제네릭 클래스의 이름은 치킨박스 이지만 정말 여기에 치킨박스만 들어갈 수 있을까요?
class ChickenBox<T> { List<T> chickenBox = new ArrayList<>(); public void add(T chicken) { chickenBox.add(chicken); } }

- 위의 그림을 보시면 치킨박스에 피자를 넣어도 들어가는 모습을 확인할 수 있습니다. 뿐만 아니라 타입에 제한이 없을 경우에는 Object 타입으로 제한되기 때문에 모든 오브젝트 타입이 들어갈 수 있게 됩니다.
- 따라서 치킨박스에는 무조건 치킨만 들어가게 할거야 ! 라고 제한을 주고 싶을 때 “바운디드 타입"을 사용할 수 있습니다.
- 바운디드 타입은 특정 타입의 서브 타입으로 제한할 수 있습니다.
class ChickenBox<T extends Chicken> { List<T> chickenBox = new ArrayList<>(); public void add(T chicken) { chickenBox.add(chicken); } }

- 위 처럼 코드를 수정하면 치킨박스의 클래스는 Chicken의 서브 타입만 허용할 수 있게됩니다.
와일드 카드란?
- 제네릭으로 구현된 메서드의 경우 선언된 타입으로만 매개변수를 입력해야한다.
- 이를 상속받은 클래스 혹은 부모클래스를 사용하고 싶어도 불가능하고 어떤 타입이 와도 상관없는 경우에 대응하기에 좋지 않으며 이러한 해결 방법으로 와일드 카드가 등장하게 된다.
언바운드디드 와일드카드(Unbounded WildCard)
- 언바운디드 와일드카드는 List<?>와 같은 형태로 물음표만 가지고 정의되어지게 된다.
- 내부적으로 Object로 정의되어 있어서 사용하고 있는 모든 타입의 인자를 받을 수 있다.
- 타입 파라미터에 의존하지 않는 메서드만을 사용하거나 Object 메서드에서 제공하는 기능으로 충분한 경우 사용하게 된다.
- 타입 파라미터에 의존적이지 않는 일반 클래스의 메서드
- List.clear, List.size 등등등
바운드디드 와일드카드(bounded WildCard)
Upper Bounded WildCard (Read)
(1부그림 인용)


- Upper Bounded WildCard는 List<? extends Chicken>의 형태로 사용한다.
- 특정 클래스의 자식 클래스만 인자로 받겠다는 의미이다.
- List<Chicken>, List<BBQChicken>, …
- 임의 Chicken 클래스를 상속받아 어느 클래스가 와도 되지만 사용할 수 있는 기능은 Chicken 클래스에 정의된 기능만 사용할 수 있다.
Lower Bounded WildCard (Write)
(1부그림 인용)


- Lower Bounded WildCard는 List<? super Chicken>의 형태로 사용한다.
- Upper Bounded WildCard와 다르게 특정 클래스의 부모 클래스만 인자로 받는다는 것이다.
- List<Chicken>, List<Object>
재귀적 타입 바운드
- 재귀적 타입 바운드는 타입 매개변수가 자신을 포함하는 수식에 의해 한정되는 것을 말한다.
public interface Comparable<T> { public int compareTo(T o); }
- 주로 List에서 Comparable 인터페이스를 구현해서 목록을 정렬, 검색, 최소 최대 값을 구할때 많이 사용하고 있습니다. 결국 List는 모든 목록 요소들이 비교가 가능해야 하며 이 요소들이 비교가 가능하다는 것을 표현하기 위해 아래의 코드처럼 재귀적 타입으로 적용할 수 있습니다.
public static <T extends Comparable<T>> T max(List<T> list) { Iterator<T> i = list.iterator(); T result = i.next(); while(i.hasNext()) { T t = i.next(); if(t.compareTo(result) > 0) { result = t; } } return result; }
- T가 자신을 포함하는 수식 Comparable<T>에 의해 한정되는 것
- 자신과 비교될 수 있는 모든 타입
인터페이스에 대한 Upper Lower Bounded Wildcard
- 클래스 뿐만 아니라 인터페이스에 대해서도 적용된다.
제네릭 클래스 선언 시 extends 는 사용할 수 있지만, super 는 사용할 수 없습니다.
// 인터페이스도 가능합니다. class EE <T extends Collector> { } // 일반 클래스도 가능합니다. class AA <T extends Number> { } // ?는 클래스 정의에서 쓸 수 없다. class BB<? extends Number> { } // 안됨 class CC <T super Number> { } // 안됨 class DD <? super Number> { }
제네릭 메서드
- 메서드의 선언부에 제네릭 타입이 선언된 메서드를 제네릭 메서드라고 한다.
- 주로 사용되는 컬렉션의 메서드 Collectios.sort() 메서드가 바로 제네릭 메서드이다.
- 제네릭 타입의 선언 위치는 반환 타입 바로 앞에 명시해주면 된다
static <T> void sort(List<T> list, Comparator<? super T> c)
- 참고로 클래스에 정의된 타입 매개변수와 제네릭 메서드에 정의된 타입 매개변수는 전혀 별개의 것이다.
- 즉 같은 타입 문자 T를 사용해도 같은 것이 아니다 !
class ChickenBox<T> { static <T> void sort(List<T> list, Comparator<? super T> c) { } }
- static 멤버에는 타입 매개변수를 사용할 수 없지만.
- 메서드에 제네릭 타입을 선언하고 사용하는 것은 가능하다.
메서드에 선언된 제네릭 타입은 지역 변수를 선언한 것과 같다고 생각하면 이해가 쉽다.
이 타입 매개변수는 메서드 내에서만 지역적으로 사용될 것이므로 메서드가 static이건 아니건 딱히 상관없다.
Q&A
- List는 구체화 타입인가?
- 제네릭이 도입된 이유는 컴파일 타임에 타입에 대한 안정성을 보장 받기 위해 사용을 하며 제네릭 타입으로 선언한 변수는 컴파일 타임에 타입 체크를 하기 때문에 보장 받을 수 있습니다.
- 로 타입으로 사용될 경우에는 안정성이라는 장점을 얻을 수 없으며 IDE에서도 경고를 해주고 있습니다.
- 로 타입을 지원하는 이유는 바로 타입 소거와 하위 호환성이 있습니다.
- 제네릭을 도입하고 이전 버전의 코드를 수용하면서 제네릭을 사용하기 위해 호환성을 유지했어야 합니다. 따라서 로 타입을 지원하면서 제네릭을 구현할 때 컴파일 시점에 타입을 소거하는 방식으로 동작하게 됩니다.
psvm { List lists // 이 리스트는 구체화인가? 비 구체화인가? }
- 결론적으로 저의 개인적인 의견을 말씀드리면 로 타입의 리스트 자체만으로는 구체화 타입이지 않을까 생각합니다. 제네릭 타입을 사용하지 않았고 List 자체는 결국 타입이 명시되어 있기 때문에 구체화 이지 않을까 생각합니다.
- 또한 비구체화는 타입 소거가 되기 때문에 런타임 시에 정보를 더 적게 가지는데 List만 선언한다면 동일하기 때문에 로 타입으로 선언된 것은 구체화라고 생각합니다.
- 비 구체화 문제는 자바에서 제네릭을 사용하지 않는 버전 즉 하위호환성을 위해서 제공했던 기능 때문에 발생했으며 하위호환성을 위해 객체의 타입 정보를 컴파일 시에 확인하여 런타임 시 동일한 동작을 보장받게 한다 ??
- 이 말은 비 구체화는 자바에서 하위 호환성을 위해서 런타임이 아니라 컴파일 시에 타입을 확인하여 런타임에는 타입 소거자를 통해 타입을 소거한 후 동일한 동작을 보장받도록 한다는 말을 제가 조금 이상하게 적었던 것 같습니다.
멘토님 피드백
- extends, 와일드카드에 대한 이해를 돕기위해 그림을 추가하자
- 1부에 있는 그림을 사용해도 된다. 그림(1부의 그림 인용)