본문 바로가기
  • soobinhand의 기술 블로그
도서/자바 프로그래밍 언어 - James Gosling

[자바 프로그래밍 언어] 11장 제네릭 타입

by soobinhand 2021. 12. 31.
728x90

11.1     제네릭 타입 선언

  • 포괄적인 참조로 Object를 사용하는 것은 위험하다.
  • 특정 타입의 요소를 저장할 수 있는 클래스를 각각 만드는 것은 비효율적이며 중복된 코드를 산출하게 만든다.
  • 어떤 종류의 객체라도 저장할 수 있으며 저장되는 타입에 따라 메소드의 반환 타입과 매개변수 타입이 정해지는 클래스를 작성하는 것이 좋다. 이것이 제네릭 타입이 만들어진 이유다.
  • Cell클래스를 Cell<E> 클래스로 다시 작성하였으며 E의 Cell이라고 읽는다. E는 Cell 객체를 저장할 수 있는 요소 타입을 나타낸다. Cell의 제네릭 버전에서 E는 이전 Cell에서 Object를 사용했던 곳에는 어디든지 사용할 수 있다. E는 타입 변수라고 하며, 요소의 약자이다. 편의상, 타입 변수들은 단일 문자 이름을 가진다. 요소 타입을 위해서는 E, 키 타입을 위해서는 K, 값 타입을 위해서는 V, 일반 타입을 위해서는 T를 사용한다.
  • 타입 변수 E를 타입 매개변수라고 하며, E는 Cell클래스를 제네릭 클래스로 만들어 주는 역할을 한다.
  • 제네릭 타입 선언에는 콤마로 분리된 여러 타입 매개변수를 선언할 수 있다.
  • Map 인터페이스는 키와 값을 서로 연결하여 정의하는 인터페이스이다. 그래서 인터페이스는 interface Map<K, V>로 선언되어 있다. 여기서 K는 키 타입을 위한 타입 매개변수이고 V는 값 타입을 위한 타입 매개변수이다.
  • 타입 매개변수 E를 가진 제네릭 클래스는 정적 타입 필드, 정적 메소드, 정적 초기자 내에서 E를 사용할 수 없다. 이렇게 사용하기 위해서는 각각의 E를 위한 필드와 메소드가 따로 필요하다. 하지만 클래스, 정적 필드, 정적 메소드는 오직 한 개씩만 존재할 수 있기에 E의 값에만 의존할 수 없다. 정적 멤버들이 절대 특정 매개변수화에 의존할 수 없다는 것은 매개변수화된 타입 이름으로 정적 멤버를 참조할 수 없다는 규칙을 강화한다.
  • 제네릭 클래스 정의 내에서라면 타입 매개변수를 비정적 선언 내의 어디든지 선언할 수 있다. 그래서 필드 선언, 메소드 반환 타입, 매개변수 타입 선언, 지역 변수 선언, 중첩 타입 선언에 타입 매개변수를 선언할 수 있다. 
  • 클래스 이름을 사용할 수 있으나 타입 매개변수를 사용할 수 없는 곳은 객체나 배열을 생성할 때 딱 두 경우뿐이다.
  • E를 인스턴스화 하거나 E의 배열을 생성할 수 없다.
  • 제네릭은 캐스트를 내부적으로 최적화한 축약형이라고도 생각할 수 있다.
  • Value클래스를 비교할 수 있도록 하기 위해서는 아래처럼 선언해야 한다.
  • class Value implements Comparable<Value>{}
  • 아래 코드에서 E에는 Comparable<E>를 확장하는 타입이라면 어떤 타입의 인자라도 올 수 있다. 다만 Comparable 인터페이스의 메소드를 제공해야 한다. 그래서 Comparable을 타입 E의 상위 경계라고 하고 E를 경계 타입 매개변수라 한다.
  • interface SortedCollection<E extends Comparable<E>>{}
  • 중첩 타입은 타입 변수를 가진 제네릭 타입으로도 선언할 수 있다. 왜냐하면 정적 멤버들은 해당 클래스의 타입 변수를 참조할 수 없기에 정적 중첩 타입 내의 타입 변수는 외부 타입에 있는 타입 변수와는 구별된다.
  • 중첩 타입이 제네릭이 될 경우 더 많은 문제가 생길 수 있다.

11.2     제네릭 타입으로 작업하기

  • 제네릭 타입으로 작업하는 것은 제네릭이 아닌 타입으로 작업하는 것보다 더 많은 준비가 필요하다.
static double sum(List<? extends Number> list){
	double sum = 0.0;
    for(Number n : list){
    	sum += n.doubleValue();
    }
    return sum;
}
  • 이 코드의 와일드카드는 타입이 Number거나 Number의 서브 클래스라면 어떤 타입이라도 List에 저장할 수 있다는 것을 의미한다. 더 정확히 말하면 이 코드에서 사용한 와일드카드는 경계 와일드카드로서 Number가 List에 올 수 있는 상위 타입이 된다. 즉, 어떤 타입이 오든지 이 타입은 적어도 Number가 되어야 한다.
  • 여기서 List<?>가 List<Object>가 아닌 List<? extends Object>를 의미한다는 것을 기억해야 한다.
  • 다음 그림을 보면 이 상속 관계가 명확해질 것이다.

  • 와일드카드의 속성은 제네릭 타입을 효율적으로 사용할 수 있게 해주는데 유용할 뿐 아니라 필수적이기도 함.

11.3     제네릭 메소드와 생성자

  • 없음.

11.4     와일드카드 캡처

  • 와일드카드는 미확인 타입을 나타낸다. 하지만 와일드카드를 가진 변수가 사용될 때마다 컴파일러는 이를 어떤 특정 타입을 가지는 것으로 다뤄야만 한다. 즉, 컴파일러는 올바르게 사용되었는지를 검사할 수 있다. 이러한 특정 타입은 와일드 카드의 캡처로 참조된다. 와일드카드 캡처를 가장 일반적으로 마주칠 수 있는 곳은 매개변수화된 타입을 잘못된 방법으로 사용했을 때 컴파일러가 발생시키는 오류 메시지이다.

11.5     제거와 원시 타입

  • 제네릭 타입인 다른 종류의 매개변수화된 타입이 여러 개 있더라도 클래스는 제네릭 타입에 대해 오직 한 개만 존재한다.
  • 컴파일러는 클래스를 컴파일할 때 모든 제네릭 타입 정보를 제거한다. 제네릭이나 매개변수화된 타입을 제거한다는 것은 단순히 타입 이름만을 남겨둔다는 것이다. 이를 원시 타입이라고 한다.
  • new List<String>[1]은 올바르지 않지만 new List<?>[1]은 올바름.
  • List<?>[1]을 생성하는 것은 실제로 List[1]을 생성하는 것과 동일하다. List[1]의 구성요소 타입은 원시 타입이다. 하지만 이 두 형식 사이에 큰 차이점이 있다. 원시 타입은 불안전한 연산을 허용하고 컴파일러는 이에 대해 경고만을 발생시킨다. 반면에 와일드카드 형식은 불안전한 연산을 금지하며 컴파일 시에 오류를 발생시킨다.

11.6     적당한 메소드 찾기

  • 없음.

11.7     클래스 확장과 제네릭 타입

  • 없음.
728x90

댓글