5장 — 제네릭

규칙 23 새 코드에는 무인자 제네릭 자료형을 사용하지 마라

  • 선언부에 형인자(type parameter)가 포함된 클래스나 인터페이스는 제네릭(generic) 클래스나 인터페이스라고 부름
  • 무인자 자료형을 쓰면 형 안전성이 사라지고, 제네릭의 장점 중 하나인 표현력(expressiveness) 측면에서 손해를 보게 된다.
  • List와 같은 무인자 자료형을 사용하면 형 안전성을 잃게 되지만, List<Object> 와 같은 형인자 자료형을 쓰면 그렇지 않다.
  • List와 List<Object> 사이에는 무슨 차이가 있나? 간단히 말해서 List는 형 검사 절차를 완전히 생략한 것이고, List<Object>는 아무 객체나 넣을 수 있다는 것을 컴파일러에게 알리는 것
  • 제네릭에 대한 하위 자료형 정의 규칙에 따르면 List<String>은 List의 하위 자료형(subtype)이지만 List<Object>의 하위 자료형은 아니기 때문
  • 비한정적 와일드카드 자료형과 무인자 자료형의 차이
    • 와일드카드 자료형은 안전하지만 무인자 자료형은 그렇지 않다.
    • 무인자 자료형 컬렉션에는 아무 객체나 넣을 수 있어서, 컬렉션의 자료형 불변식(type invariant)이 쉽게 깨진다.
    • Collection<?>에는 null 이의의 어떤 원소도 넣을 수 없다.
    • Collection<?>에는 어떤 자료형의 객체를 꺼낼 수 있는지도 알 수 없다.
  • 무인자 자료형을 사용하는 예외 두 가지
    • 제네릭 자료형 정보가 프로그램이 실행될 때 지워짐 Erasure
    • 클래스 리터럴(class literal) 에는 반드시 무인자 자료형을 사용해야 한다.
    • 제네릭 자료형에 instanceof 연산자를 적용할 때는 다음과 같이 하는 것이 좋다.
// instanceof 연산자에는 무인자 자료형을 써도 OK
if (o instanceof Set) {   //무인자자료형
  Set<?> m = (Set<?>) o;  //와일드카드자료형
}
public class Raw {

    public static void main(String[] args) {
        List<String> strings = new ArrayList<string>();
        unsafeAdd(strings, new Integer(42));
        String s = strings.get(0);
    }

    private static void unsafeAdd(List list, Object o) {
        list.add(o);
    }
}
용어 규칙
형인자 자료형(parameterized type) List(String) 23
실 형인자(actual type parameter) String 23
제네릭 자료형(generic type) List 23, 26
형식 형인자 (formal type parameter) E 23
비한정적 와일드카드 자료형 List<?> 23
무인자 자료형(raw type) List 23
한정적 형인자(bounded type parameter) 26
재귀적 형 한정(recursive type bound) <T extends Comparable> 27
한정적 와일드카드 자료형(bounded List<? extends Number> 28
제네릭 메서드(generic method) static List asList<E[] a> 27
자료형 토큰(type token) String.class 29

규칙 24 무점검 경고(unchecked warning)를 제거하라

  • 모든 무점검 경고는, 가능하다면 없애야 한다.
  • 제거할 수 없는 경고 메시지는 형 안전성이 확실할 때만 @SuppressWarnings(“unchecked”) 어노테이션(annotation)을 사용해 억제하기 바란다.
  • SuppressWarnings 어노테이션은 개별 지역 변수 선언부터 클래스 전체에까지, 어떤 크기의 단위에도 적용할 수 있다. 하지만 @SuppressWarnings 어노테이션은 가능한 한 작은 범위에 적용하라.
  • SuppressWarnings 어노테이션은 return 문에 붙일 수 없는데, 선언문 이 아니기 때문이다.JLS, 9.7
public <T> T[] toArray(T[] a) {
  if(a.length < size) {
    @SuppressWarnings("unchecked") T[] result = (T[]) Arrays.copyOf(elements, size, a.getClass());
    return result;
  }
  System.arraycopy(element, 0, a, 0, size);
  if (a.length > size)
    a[size] = null;
  return a;
}
  • @SuppressWarnings(“unchecked”) 어노테이션을 사용할 때마다, 왜 형 안전성을 위반하지 않는지 밝히는 주석을 반드시 붙이라.

규칙 25 배열 대신 리스트를 써라

  • 배열은 제네릭 자료형과 두 가지 중요한 차이점
    • 배열은 공변 자료형(covariant), 제네릭은 불변 자료형(invariant)
      • Sub가 Super의 하위 자료형(subtype)이라면 Sub[]도 Super[]의 하위 자료형이라는 것이다.
      • Type1과 Type2가 있을 때, List<Type1>은 List<Type2>의 상위 지료형이나 하위 자료형이 될 수 없다.
    • 배열은 실체화(reification) 되는 자료형
      • 배열의 각 원소의 자료형은 실행시간(runtime)에 결정된다는 것이다.
      • 제네릭은 삭제(erasure) 과정을 통해 구현된다.
      • 자료형에 관계된 조건들은 컴파일 시점에만 적용되고, 그 각 원소의 자료형 정보는 프로그램이 실행될 때는 삭제된다는 것이다.
      • 자료형 삭제(erasure) 덕에, 제네릭 자료형은 제네릭을 사용하지 않고 작성된 오래된 코드와도 문제 없이 연동한다.
      • new List<E>[], new List<String>[], new E[]는 전부 컴파일되지 않는 코드다. 컴파일하려고 하면 제네릭 배열 생성(generic array creation)이라는 오류가 발생할 것이다.
      • E, List<E>, List<String>와 같은 자료형은 실체화 불기능(non-refiable) 자료형으로 알려져 있다.
      • 프로그램이 실행될 때 해당 자료형을 표현하는 정보의 양이 컴파일 시점에 필요한 정보의 양보다 적은 자료형이 실체화 불기능 자료형이다.
      • 제네릭 자료형에 담긴 원소들의 자료형으로 만든 배열을 반환하는 것은 일반적으로 불가능하다.
  • covariant : Meaning, the subtyping relation of the simple types are preserved for the complex types. While “function from Animal to String” is a subtype of “function from Cat to String” because the function type constructor is contravariant in the argument type. Here the subtyping relation of the simple types is reversed for the complex types.
  • invariant : In computer programming, specifically object-oriented programming, a class invariant (or type invariant) is an invariant used to constrain objects of a class.
  • 항등원(恒等元,Identity element)은 군론 등의 대수학에서 다루는 기본적인 개념으로, 집합의 어떤 원소와 연산을 취해도, 자기 자신이 되게하는 원소를 말한다. 항등원이 무엇인지는 집합과 이항연산의 종류에 따라 달라진다.

규칙 26 가능하면 제네릭 자료형으로 만들 것

  • 클래스를 제네릭화하는 첫 번째 단계는 선언부에 형인자(type parameter)를 추가하는 것이다.
  • 배열을 사용하는 제네릭 자료형 구현
    • Object 배열을 만들어서 제네릭 배열 자료형으로 형변환
    • E[]에서 Object[]으로 변경
  • 무점검 형변환(unchecked cast) 경고 억제의 위험성은 스칼라(scalar) 자료형보다 배열 자료형 쪽이 더 크기 때문에 두번째 해법이 더 낫다고 볼 수도 있다.

  • Q : ‘순서는 어떤 JDK냐에 따라 달라질 수 있다.’

규칙 27 가능하면 제네릭 메서드로 만들 것

  • 형인자를 선언하는 형인자 목록(type parameter list)은 메서드의 수정자(modifier)와 반환값 자료형 사이에 둔다.
  • 제네릭 싱글턴 패턴(generic singleton pattern)이 있다. 때로는 변경이 불가능하지만 많은 자료형에 적용 기능한 객체를 만들어야 할 때 가 있다.
  • 형인자가 포함된 표현식으로 형인자를 한정하는 것도 가능하다. 이런 용법을 재귀적 자료형 한정(recursive type bound)이라 한다.
  • 컴파일러가 자료형을 정확히 유추하지 못할 경우에는 명시적 형인자(explicit type parameter)를 통해 어떤 자료형을 쓸지 알려줄 수 있다.
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;

import java.util.HashMap;

public class ExplicitType {
  public void explicit(HashMap<String, Number> param) {
    param = Maps.newHashMap(ImmutableMap.<String, Number>builder().put("A", 1).put("B", 2).build());
  }
}
  • Q 자료형 유추 (type inference)
  • Q 항등함수(identity function)
  • Q 무상태(stateless) 함수

규칙 28 한정적 와일드카드를 써서 API 유연성을 높여라

  • 유연성을 최대화하려면, 객체 생산자(producer)나 소비자(consumer) 구실을 하는 메서드 인자의 자료형은 와일드카드 자료형으로 하라는 것이다.
  • PECS (Produce - Extends, Consumer - Super)
  • 반환값에는 와일드카드 자료형을 쓰면 안 된다.
  • 클래스 사용자가 와일드카드 자료형에 대해 고민하게 된다면, 그것은 아마도 클래스 API가 잘못 설계된 탓일 것이다.
  • 컴파일러가 자료형을 정확히 유추하지 못할 경우에는 명시적 형인자(explicit parameter)를 통해 어떤 자료형을 쓸지 알려줄 수 있다.
  • Comparable<T> 대신 항상 Comparable<? super T>를 사용해야 한다.
  • 형인자가 메서드 선언에 단 한군데 나타난다면 해당 인자를 와일드카드로 바꾸라는 것이다.
  • 생산자는 extends이고 소비지는 super라는(PECS) 기본적 규칙을 암기하라.
  • 모든 Comparable과 Comparator는 소비지라는 것도 기억하자.

  • R The Get and Put Principle: use an extends wildcard when you only get values out of a structure, use a super wildcard when you only put values into a structure, and don’t use a wildcard when you both get and put.
  • Q 이원성(duality) : 수학과 물리학에서 자주 등장하는 표현이다. 보통 어떤 수학적 구조의 쌍대(雙對; dual)란 그 구조를 ‘뒤집어서’ 구성한 것을 말하는데, 엄밀한 정의는 세부 분야와 대상에 따라 각각 다르다. 쌍대의 쌍대는 자기 자신이므로 어떤 대상과 그 쌍대는 서로 일종의 한 ‘켤레’를 이룬다고 할 수 있으며, 이를 쌍대관계(雙對關係)라고 한다.

규칙 29 형 안전 다형성 컨테이너를 쓰면 어떨지 따져보라

  • 컨테이너 대신 키(key)에 형인자를 지정하는 것이 기본적 아이디어다.
  • 자바 1.5부터 Class가 제네릭 클래스가 되었으므로 가능하다.
  • class 리터털의 자료형은 더 이상 Class가 아니며, Class<T>다. 예를 들어 String.class의 자료형은 Class<String>이고 Integer.class의 자료형은 Class<Integer>다.
  • 컴파일 시간 자료형이나 실행시간 자료형 정보를 메서드들에 전달할 목적으로 Class 리터럴을 이용하는 경우, 그런 Class 리터럴을 자료형 토큰(type token)이라 부른다.