본문 바로가기

STUDY REVIEW/이펙티브 자바 독서스터디

[EFFECTIVE JAVA] 이펙티브 자바 독서스터디 - 5장 제네릭

5장 제네릭
아이템 26. 로 타입은 사용하지 말라
아이템 27. 비검사 경고를 제거하라
아이템 28. 배열보다는 리스트를 사용하라
아이템 29. 이왕이면 제네릭 타입으로 만들라
아이템 30. 이왕이면 제네릭 메서드로 만들라
아이템 31. 한정적 와일드카드를 사용해 API 유연성을 높이라
아이템 32. 제네릭과 가변인수를 함께 쓸 때는 신중하라
아이템 33. 타입 안전 이종 컨테이너를 고려하라

읽고 느낀 점

 

이해를 돕기위한 코드를 읽어나가니 어느 정도 맛보기로 이해가 되는것 같다(?) 다행인 것은 출판된지 오래되어 좋은 블로그 예시문들이 많다는 것이다. 코드를 보니 어느 정도 이해가 되는것 같아 핵심 문구와 코드만 정리를 해놓았다. 만약 다른날 이 코드가 필요해진다면 코드만 체크하기 위해 기록을 남긴다.


아이템 26. 로 타입은 사용하지 말라

로 타입을 사용하면 런타임에 예외가 일어날 수 있으니 사용하면 안된다.
Set<Object>는 매개변수화 타입이고, Set<?>은 모종의 타입 객체만 저장할 수 있는 와일드 카드 타입이다.
위 둘은 안전하지만 로 타입은 안전하지 않다. 

 

//비한정적 와일드 카드 타입을 사용하자
static int num(Set<?> s1, Set<?> s2) {...}
//로 타입으로 써도 좋은 예 - instanceof 연산자
if (o instanceof Set) {		//로 타입
	Set<?> s = (Set<?>) o;	//와일드카드 타입
    ...
}

아이템 27. 비검사 경고를 제거하라

비검사 경고는 중요하나 무시하지는 말자.
모든 비검사 경고는 런타임에 ClassCastException을 일으킬 수 있는 잠재적 가능성을 뜻하니 최선을 다해 제거하자. 경고를 없앨 수 없다면 그 코드가 타입 안전함을 증명하고 @SuppressWarnings("unchecked") 애너테이션으로 경고를 숨기고 주석을 남기자.

 

* 절대로 클래스 전체에 적용하지 말 것

 

//지역변수를 추가해 @SuppressWarnings의 범위를 좁힌다.
public <T> T[] toArray(T[] a) {
	
    if(a.length < size) {
    
    	//생성한 배열과 매개변수로 받은 배열의 타입이 모두 T[]로 같으므로 올바른 형변환이다.
    	@SuppressWarnings("unchecked") T[] result = 
        	(T[]) Arrays.copyOf(elements, size, a.getClass());
        return result;
    }
    
    System.arraycopy(elements, 0, a, 0, size);
    
    if(a.length > size)
    	a[size] = null;
	return a;
}

아이템 28. 배열보다는 리스트를 사용하라

배열과 제네릭에는 다른 타입 규칙이 적용된다.
배열은 공변이고 실체화 되다.
제네릭은 불공변이고 타입 정보가 소거된다.
그 결과 배열은 런타임에는 타입이 안전하지만 컴파일타임에서는 그렇지 않다. 제네릭은 반대이다.
둘을 섞어 쓰다가 오류가 나면 가장 먼저, 배열을 리스트로 대체하는 방법을 생각하자.

 

* 배열은 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인한다.

* 배열의 원소가 실체화 불가타입일 경우 @SafeVarargs 애너테이션으로 대처할 수 있다.

 

//리스트 기반 Chooser - 타입 안전성 확보
public class Chooser<T> {
	
    private final List<T> choiceList;
    
    public Chooser(Collection<T> choices) {
    	choiceList = new ArrayList<>(choices);
    }
    
    puvlic T choose() {
    	Random rnd = ThreadLocalRandom.current();
        return choiceList.get(rnd.nextInt(choiceList.size()));
    }
}

아이템 29. 이왕이면 제네릭 타입으로 만들라

새로운 타입을 설계할 때는 형변환 없이도 사용할 수록 하자.
기존 클라이언트에 아무 영향을 주지 않으면서, 새로운 사용자를 편하게 해주는 제네릭 타입으로 변경하자.

 

* 현업에서는 첫번쨰 방식을 선호하며 자주 사용한다.

* 힙 오염에 반응하는 개발자는 두번쨰 방식을 고수하기도 한다.

 

//배열을 사용한 코드를 제네릭으로 만드는 방법 1

// 배열 elements는 push(E)로 넘어온 E 인스턴스만 담는다.
// 타입 안전성을 보장하지만, 이 배열의 런타임은 Object[]이다.
@SuppressWarnings("uncheked")
public Stack() {
	elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
//배열을 사용한 코드를 제네릭으로 만드는 방법 2

//비검사 경고를 적절히 숨긴다.
public E pop() {

	if (size == 0)
    	throw new EmptyStackException();
        
	//push에서 E 타입만 허용하므로 이 형변환은 안전하다.
    @SuppressWarnings("uncheked") E result = (E) elements[--size];
    
    elements[size] = null; //다 쓴 참조 해제
    return result;
}
//제네릭 Stack을 사용한 맛보기 프로그램
public static void main(String[] args) {
	
    Stack<String> stack = new Stack<>();
    
    for (String arg : args)
    	stack.push(arg);
        
	while (!stack.isEmpty())
    	System.out.println(stack.pop().toUpperCase());
}

아이템 30. 이왕이면 제네릭 메서드로 만들라

메서드도 형변환 없이 사용할 수 있는 편이 좋다. 그럴 경우 많은 경우가 제네릭 메서드가 되야 한다.

 

*예시 참조

 

//제네릭 메서드
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {

  Set<E> result = new HashSet<>(s1);
  result.addAll(s2);
  return result;
}
//제네릭 싱글턴 팩터리 패턴
private static UnaryOperator<Object> IDENTITY_FN = (t) -> t;

  @SuppressWarnings("unchecked")
  public static <T> UnaryOperator<T> identityFunction() {
  return (UnaryOperator<T>) IDENTITY_FN;
}
//제네릭 싱글턴을 사용하는 예
public static void main(String[] args) {

  String[] strings = {"삼베", "대마", "나일론"};
  UnaryOperator<String> sameString = identityFunction();
  
  for (String s : strings) 
 	 System.out.println(sameString.apply(s));
  

  Number[] numbers = {1, 2.0, 3L};
  UnaryOperator<Number> sameNumber = identityFunction();
  
  for (Number n : numbers) 
  	System.out.println(sameNumber.apply(n));
}
//재귀적 타입 한정
public static <E extends Comparable<E>> E max(Collection<E> c);
//컬렉션에서 최댓값 반환한다. - 재귀적 타입 한정 사용
public static <E extends Comparable<E>> E max(Collection<E> collection) {

  if (collection.isEmpty()) 
  	throw new IllegalArgumentException("컬렉션이 비어 있습니다.");
  

  E result = null;
  
  for (E e : collection) 
    if (result == null || e.compareTo(result) > 0) 
    	result = Objects.requireNonNull(e);
  
  return result;
}

아이템 31. 한정적 와일드카드를 사용해 API 유연성을 높이라

조금 복잡해도 와일드카드 타입을 적용하면 API가 유연해진다. 
많이 쓰일 라이브러리를 작성한다면 와일드카드 타입을 적절히 사용하자.
PECS 공식!
생산자(PRODUCER)는 EXTENDS 소비자(CONSUMER)는 SUPER !!
Comparable, Comparator은 모두 소비자이다!

 

* 컴파일러가 올바른 타입을 추론하지 못할 때면 명시적 타입 인수를 사용해서 타입을 알려주자.

 


아이템 32. 제네릭과 가변인수를 함께 쓸 때는 신중하라

메서드에 제네릭(또는 매개변수화) varargs 매개변수를 사용한다면, 먼저 그 메서드 타입이 안전한지 확인한 다음 @SafeVarargs 애너테이션을 달아 사용하는데 불편하지 않게 하자

https://jithub.tistory.com/333

 

 

[이펙티브 자바] item 32 - 제네릭과 가변인수를 함께 쓸 때는 신중하라

■가변인수란? 가변인수는 메서드에 넘기는 인수의 개수를 클라이언트가 조절할 수 있게 해주는 기법이다. static void test(List ... param) { } 가변인수 메서드는 제네릭과 함께 자바5에 추가되었다.

jithub.tistory.com


아이템 33. 타입 안전 이종 컨테이너를 고려하라

타입 안전 이종 컨테이너는 Class를 키로 쓰며 이런 Class 객체를 타입 토큰이라고 한다.
또한, 직접 구현한 키 타입도 쓸 수 있다.
데이터베이스의 행을 표현한 DatabaseRow 타입에는 제네릭 타입인 Column<T>을 사용하자.

 

// 타입 안전 이종 컨테이너 패턴 - 구현
public class Favorites {

  private Map<Class<?>, Object> favorites = new HashMap<>();
  
  public <T> void putFavorite(Class<T> type, T instance) {
    favorites.put(Objects.requireNonNull(type), type.cast(instance));
  }
  
  public <T> T getFavorite(Class<T> type) {
    return type.cast(favorites.get(type));
  }
}
//컴파일 시점에는 타입을 알 수 없는 애너테이션을 asSubClass 메서드를 사용해 런타임에 읽어내는 예
//이 메서드는 오류나 경고 없이 컴파일된다.

static Annotation getAnnotation(AnnotatedElement element, String annotationTypeName) {

    Class<?> annotationType = null; // 비한정적 타입 토큰 
    
    try {
        annotationType = Class.forName(annotationType);
    } catch (Exception ex) {
        throw new IllegalArgumentException(ex);
    }
    
    return element.getAnnotation(
        annotationType.asSubclass(Annotation.class)
    );
}