EffectiveJava

[EffectiveJava] 5장 제네릭

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

// 26-1 컬렉션의 로 타입 - 따라 하지 말 것!
// Stamp 인스턴스만 취급한다.
private final Collection stamp=...;
// 실수로 동전을 넣는다.
stamps.add(new Coin(...)); // "unchecked call" 경고를 내뱉는다.
// 26-2 반복자의 로 타입 - 따라 하지 말 것!
for (Iterator i = stamps.iterator(); i.hasNext();) {
  Stamp stamp = (Stamp) i.next(); //ClassCastException을 던진다.
  stamp.cancel();
}
// 26-3 매개변수화된 컬렉션 타입 - 타입 안전성 확보!
private final Collection<Stamp> stamps = ...;
// 26-4 런타임에 실패한다. - unsafeAdd 메서드가 로 타입(List)을 사용
public static void main(String[] args){
    List<String> strings = new ArrayList<>();
        unsafeAdd(strings, Integer.valueOf(42));
        String s = strings.get(0); //컴파일러가 자동으로 형변환 코드를 넣어준다.
    }

    // 로 타입
    private static void unsafeAdd(List list, Object o) {
        list.add(o);
    }
}
// 26-5 잘못된 예 - 모르는 타입의 원소도 받는 로 타입을 사용했다
static int numElementsInCommon(Set s1, Set s2) {
	int result = 0;
	for (Object o1 : s1) 
		if (s2.contains(o1)) result++;
	return result;
}
// 26-6 비한정적 와일드카드 타입을 사용하라 - 타입 안전하며 유연하다
static int numElementsInCommon(Set<?> s1, Set<?> s2) {...}
// 26-7 로 타입을 써도 좋은 예 - instanceof 연산자
if (o instanceof Set) { // 로 타입
	Set<?> s = (Set<?>) o; // 와일드 카드
}

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

Set<Lark> exaltation = new HashSet();
public <T> T[] toArray(T[] a ){
    if (a.length < size) {
        return (T[]) Arrays.copyOf(elements, size, a.getClass());
    }
    System.array copy(elements, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}
// 27-1 지역변수를 추가해 @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. 배열보다는 리스트를 사용하라

// 28-1 런타임에 실패한다.
Object[] objectArray = new Long[1];
ObjectArray[0] = "타입이 달라 넣을 수 없다."; // ArrayStoreException을 던진다.
// 28-2 컴파일되지 않는다!
List<Object> ol = new ArrayList<Long>(); // 호환되지 않는 타입이다.
ol.add("타입이 달라 넣을 수 없다.");
// 28-3 제네릭 배열 생성을 허용하지 않는 이유 - 컴파일되지 않는다.
List<String>[] stringLists = new List<String>[1]; // 허용 된다는 가정하에
List<Integer> intList = List.of(42); // 42를 생성한 intList
Object[] objects = stringLists; // object 배열에 stringLists 할당한다.
objects[0] = intList; // objects에 42를 할당한다.
String s = stringLists[0].get(0); // String만 담아있다고 했으나 int가 담겨 있어서 문제가 된다.
// 28-4 제네릭을 시급히 적용해야 한다
public class Chooser {
	private final Object[] choiceArray;

	public Chooser(Collection choices) {
		choiceArray = choices.toArray();
	}

	public Object choose() {
		Random rnd = ThreadLocalRandom.current();
		return choiceArray[rnd.nextInt(choiceArray.length)];
	}
}
// 28-5 Chooser를 제네릭으로 만들기 위한 첫 시도 - 컴파일되지 않는다.
public class Chooser<T> {
    private final List<T> choiceArray;

    public Chooser(Collection<T> choices) {
        choiceList = choices.toArray();
    }

		// choose 메서드는 그대로다.
}
choiceArray = (T[]) choices.toArray();
// 28-6 리스트 기반 Chooser - 타입 안전성 확보!
public class Chooser<T> {
    private final List<T> choiceList;

    public Chooser(Collection<T> choices) {
        choiceList = new ArrayList<>(choices);
    }

    public T choose() {
        Random rnd = ThreadLocalRandom.current();
        return choiceList.get(rnd.nextInt(choiceList.size()));
    }
}

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

// 29-1 제네릭이 절실한 강력 후보! (Object 기반 스택)
public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if(size==0) throw new EmptyStackException();
        Object result = elements[--size];
        elements[size] = null; // 다 쓴 참조 해제
        return result;
    }

    public boolean isEmpty() {
        return size==0;
    }

    private void ensureCapacity() {
        if(elements.length == size) {
            elements = Arrays.copyOf(elements, 2*size + 1);
        }
    }
}
// 29-2 
public class Stack<E> {
    private E[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

		public Stack() {
			elemnets = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
		}

    public void push(E e) {
        ensureCapacity();
        elements[size++] = e;
    }

		public E pop() {
			if(size==0) throw new EmptyStackException();

			E result = (E) elements[--size];
			elements[size] = null; // 다 쓴 참조 해제
			return result;
		}
		... // isEmpty와 ensureCapacity 메서드를 그대로다.
}
// 29-3 배열을 사용한 코드를 제네릭으로 만드는 방법 1 
@SuppressWarnings("unchecked")
public Stack(){
		elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
// 29-4 배열을 사용한 코드를 제네릭으로 만드는 방법 2
// 비검사 경고를 적절히 숨긴다
public E pop() {
	if(size==0){
		throw new EmptyStackException();
	}

	// push에서 E 타입만 허용하므로 이 형변환은 안전하다
	@SuppressWarnings("unchecked")
	E result = (E) elements[--size];

	elements[size] = null; // 다 쓴 참조 해제
	return result;
}
// 29-5 제네릭 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());
}
class DelayQueue<E extends Delayed> implements BlockingQueue<E>

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

// 30-1 로 타입 사용 - 수용 불가! (아이템 26)
public static Set union(Set s1, Set s2) {
	Set result = new HashSet(s1);
	result.addAll(s2);
	return result;
}
// 30-2 제네릭 메서드
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
    Set<E> result = new HashSet<>(s1);
    result.addAll(s2);
    return result;
}
// 30-3 제네릭 메서드를 활용하는 간단한 프로그램
public static void main(String[] args) {
    Set<String> guys = Set.of("톰", "딕", "해리");
    Set<String> stooges = Set.of("래리", "모에", "컬리");
    Set<String> aflCio = union(guys, stooges);
    System.out.println(aflCio);
}
// 30-4 제네릭 싱글턴 팩터리 패턴
private static UnaryOperator<Object> IDENTITY_FN = (t) -> t;

@SuppressWarnings("unchecked")
public static <T> UnaryOperator identityFunction() {
    return (UnaryOperator) IDENTITY_FN;
}
// 30-5 제네릭 싱글턴을 사용하는 예
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 interface Comparable<T> {
	int compareTo(T o);
}
// 30-6 재귀적 타입 한정을 이용해 상호 비교할 수 있음을 표현했다.
public static <E extends Comparable<E>> E max(Collection<E> c);
// 30-7 컬렉션에서 최댓값을 반환한다 - 재귀적 타입 한정 사용
public static <E extends Comparable<E>> E max(Collection<E> c) {
    if (c.isEmpty())
        throw new IllegalArgumentException("컬렉션이 비어 있습니다.");

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

    return result;
}

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

public class Stack<E> {
	public Stack();
	public void push(E e);
	public E pop();
	public boolean isEmpty();
}
// 31-1 와일드카드 타입을 사용하지 않는 pushAll 메서드 - 결함이 있음
public void pushAll(Iterable<? extends E> src){
		for (E e : src)
		    push(e);
}
// 31-2 E 생산자(producer) 매개변수에 와일드카드 타입 적용
public void pushAll(Iterable<? extends E> src) {
	for (E e : src) 
			push(e);
}
// 31-3 와일드카드 타입을 사용하지 않은 popAll 메서드 - 결함이 있다!
public void popAll(Collection<E> dst) {
	while (!isEmpty()) 
					dst.add(pop());
}
// 31-4 E 소비자 매개변수에 와일드카드 타입 적용
public void popAll(Collection<? super E> dst) {
	while (!isEmpty()) 
				dst.add(pop());
}
// 31-5 T 생산자 매개변수에 와일드카드 타입 적용
public Chooser(Collection<? extends T> choices)
Set<Integer> integers = Set.of(1,3,5);
Set<Double> doubles = Set.of(2.0, 4.0, 6.0);
Set<Number> numbers = union(integers, doubles);
// 31-6 자바 7까지는 명시적 타입 인수를 사용해야 한다.
Set<Number> numbers = Union.<Number>union(integers, doubles);
public static <E extends Comparable<E>> E max(List<E> list)
// 와일드카드 타입을 사용해 다듬은 모습
public static <E extends Comparable<? super E>> E max(List<? extends E> list)
// 31-7 swap 메서드의 두 가지 선언
public static <E> void swap(List<E> list, int i, int j);
public static void swap(List<?> list, int i, int j);
public static void swap(List<?> list, int i, int j) {
		swapHelper(list, i, j);
}

// 와일드카드 타입을 실제 타입으로 바꿔주는 private 도우미 메서드
private static <E> void swapHelper(List<E> list, int i, int j) {
		list.set(i, list.set(j, list.get(i)));
}

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

// 32-1 제네릭과 varargs(가변인수)를 혼용하면 타입 안정성이 깨진다
static void dangerous(List<String> ... stringLists) {
	List<Integer> inList = List.of(42);
	Object[] objects = stringLists;
	obejcts[0] = intList; // 힙 오염 발생 (List<Integer>가 들어감)
	String s = stringLists[0].get(0); // ClassCastException  
}
// 코드 32-2 자신의 제네릭 매개변수 배열의 참조를 노출한다. - 안전하지 않다! (193쪽)
static <T> T[] toArray(T... args) {
        return args;
}
static <T> T[] pickTwo(T a, T b, T c) {
	switch(ThreadLocalRandom.current().nextInt(3)) {
		case 0 : return toArray(a,b);
		case 1 : return toArray(a,c);
		case 2 : return toArray(b,c);
	}
	throw new AssertionError(); // 도달할 수 없다
}
public static void main(String[] args) {
	String[] attributes = pickTwo("좋은", "빠른", "저렴한");
}
// 32-3 제네릭 varargs 매개변수를 안전하게 사용하는 메서드
@SafeVarargs
static <T> List<T> flatten(List<? extends T>... lists) {
	List<T> result = new ArrayList<>();
	for (List<? extends T> list : lists) 
			result.addAll(list);
	return result;
}
// 32-4 제네릭 varargs 매개변수를 List로 대체한 예 - 타입 안전하다
static <T> List<T> flatten(List<List<? extends T>> lists) {
	List<T> result = new ArrayList<>();
	for (List<? extends T> list :  lists) 
				result.addAll(list);
	return result;
}
// 정적 팩터리 메서드인 Lists.of 활용 (Lists.of에 @SafeVarargs 애너테이션이 달려있음)
audience = flatten(Lists.of(friends, romans, countrymen));
// 배열 대신 List를 이용해 안전하게 바꿘 PickTwo
public class SafePickTwo {
    static <T> List<T> pickTwo(T a, T b, T c) {
        switch(ThreadLocalRandom.current().nextInt(3)) {
            case 0: return List.of(a, b);
            case 1: return List.of(a, c);
            case 2: return List.of(b, c);
        }
        throw new AssertionError();
    }

    public static void main(String[] args) {
        List<String> attributes = pickTwo("좋은", "빠른", "저렴한");
        System.out.println(attributes);
    }
}

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

// 코드33-1 타입 안전 이종 컨테이너 패턴 - API
public class Favorites {
	public <T> void putFavorite(Class<T> type, T instance);
	public <T> T getFavorite(Class<T> type);
}
// 코드33-2 타입 안전 이종 컨테이너 패턴 - 클라이언트
public static void main(String[] args) {
    Favorites f = new Favorites();
    
    f.putFavorite(String.class, "Java");
    f.putFavorite(Integer.class, 0xcafebabe);
    f.putFavorite(Class.class, Favorites.class);
   
    String favoriteString = f.getFavorite(String.class);
    int favoriteInteger = f.getFavorite(Integer.class);
    Class<?> favoriteClass = f.getFavorite(Class.class);
    
    System.out.printf("%s %x %s%n", favoriteString, favoriteInteger, favoriteClass.getName());
		// Java cafebabe Favorites 출력
}
// 코드33-3 타입 안전 이종 컨테이너 패턴 - 구현
public class Favorites {
    private Map<Class<?>, Object> favorites = new HashMap<>();

    public <T> void putFavorite(Class<T> type, T instance) {
        favorites.put(Objects.requireNonNull(type), instance);
    }

    public <T> T getFavorite(Class<T> type) {
        return type.cast(favorites.get(type));
    }  
}
// cast의 반환 타입은 Class 객체의 타입 매개변수와 같음
public class Class<T> {
	T cast(Object obf);
}
//6 호출하기전까진 아무 문제 없다가 ClassCastException (컴파일타임에 잡지못함)
f.putFavorite((Class)Integer.class, "Integer 인스턴스가 아님");
int favoriteInteger = f.getFavorite(Integer.class);

//7 컴파일도 되고 동작도 함
HashSet<Integer> set = new HashSet<>();
((HashSet).set).add("문자열");
// 33-4 동적 형변환으로 런타임 타입 안정성 확보
public <T> void putFavorite(Class<T> type, T instance) {
	favorites.put(Objects.requireNonNull(type), type.cast(instance));
}
// 33-5 asSubclass 메서드를 사용해 한정적 타입 토큰을 안전하게 형변환한다.
static Annotation getAnnotation(AnnotatedElement element,
                                    String annotationTypeName) {
        Class<?> annotationType = null; // 비한정적 타입 토큰
        try {
            annotationType = Class.forName(annotationTypeName);
        } catch (Exception ex) {
            throw new IllegalArgumentException(ex);
        }
        return element.getAnnotation(
                annotationType.asSubclass(Annotation.class));
}

Leave a Reply