JAVA/이펙티브 자바

    38. 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라

    대부분의 상항에서 열거 타입을 확장하는건 좋지 않은 생각이다. 기반 타입과 확장된타입들의 원소 모두를 순회할 방법도 마땅치 않다. 또, 확장성을 높이려면 고려할 요소가 늘어나 설계와 구현이 복잡해진다. 그런데, 확장할 수 있는 열거 타입이 어울리는 쓰임이 최소한 하나는 있다. 바로 연산 코드이다. 이따금 API가 제공하는 기본 연산 외에 사용자 확장 연산을 추가할 수 있도록 열어줘야 할 때가 있다. 기본 아이디어는 열거 타입이 임의의 인터페이스를 구현할 수 있다는 사실을 이용하는 것이다. public interface Operation { double apply(double x, double y); } public enum BasicOperation implements Operation { PLUS("+..

    37. ordinal 인덱싱 대신 EnumMap을 사용하라

    이따금 배열이나 리스트에서 원소를 꺼낼 때 ordinal 메서드로 인덱스를 얻는 코드가 있다. 이런 코드는 동작은 하지만 문제가 한가득이다. 배열은 제네릭과 호환되지 않으니 비검사 형변환을 수행해야 하고 깔끔히 컴파일되지 않는다. 배열은 각 인덱스의 의미를 모르니 출력 결과에 직접 레이블을 달아야 한다. 가장 심각한 문제는 정확한 정수값을 사용한다는 것을 개발자가 직접 보증해야 한다는 점이다. 정수는 열거 타입과 달리 타입 안전하지 않기 때문에 잘못된 동작이 발생할 수 있다. public class Plant { final String name; final LifeCycle lifeCycle; public Plant(String name, LifeCycle lifeCycle) { this.name = n..

    35. ordinal 메서드 대신 인스턴스 필드를 사용하라

    대부분의 열거 타입 상수는 자연스럽게 하나의 정숫값에 대응된다. 모든 열거 타입은 해당 상수가 그 열거 타입에서 몇 번째 위치인지 반환하는 ordinal이라는 메서드를 제공한다. 이런 이유로 열거 타입 상수와 연결된 정수값이 필요할 때 ordinal 메서드를 이용하고 싶은 유혹에 빠진다. 그러나 해당 메서드를 사용하면 유지보수가 끔찍해질 수 있다. 구현에 따라 코드가 오동작 할 수 있고, 값을 추가하거나 제거하기가 힘들어질 수 있다. 또한 중간에 값을 비워둘 수 없게되고 때문에 더미 상수를 추가해야 하는 상황이 올 수 있다. 해답은 간단하다. ordinal 메서드를 사용하지 말고 인스턴스 필드에 int 필드를 추가 후 저장하여 사용하면 된다. public enum Ensemble { SOLO(1), DU..

    34. int 상수 대신 열거 타입을 사용하라

    열거 타입은 일정 개수의 상수 값을 정의한 다음, 그 외의 값은 허용하지 않는 타입이다. 사계절, 태양계의 행성, 카드게임의 카드 종류 등이 좋은 예다. public static final int APPLE_FUJI = 0; public static final int APPLE_PIPPIN = 1; public static final int APPLE_SMITH = 2; public static final int ORANGE_NAVEL = 0; public static final int ORANGE_TEMPLE = 1; public static final int ORANGE_BLOOD = 2; 상수를 이용한 열거 패턴 기법에는 타입 안전을 보장할 방법이 없으며, 표현력도 좋지 않다. 정수 상수는 문자열로..

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

    제네릭은 Set 등의 컬렉션과 ThreadLocal, AtomicReference 등의 단일원소 컨테이너에도 흔히 쓰인다. 이런 모든 쓰임에서 매개변수화되는 대상은 컨테이너 자신이다. 따라서 하나의 컨테이너에서 매개변수화할 수 있는 타입의 수가 제한된다. 하지만 더 유연한 수단이 필요할 때도 종종 있다. 데이터베이스의 행은 임의 개수의 열을 가질 수 있는데, 모두 열을 타입 안전하게 이용하려면? 컨테이너 대신 키를 매개변수화한 다음, 컨테이너에 값을 넣거나 뺄 때 매개변수화한 키를 함께 제공하면 된다. 이렇게 하면 제네릭 타입 시스템이 값의 타입이 키와 같음을 보장해줄 것이다. 이러한 설계 방식을 타입 안전 이종 컨테이너 패턴이라 한다. public class Favorite { private Map f..

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

    가변인수 메서드와 제네릭은 잘 어우러지지 않는다. 가변인수 메서드를 호출하면 가변인수를 담기 위한 배열이 자동으로 하나 만들어진다. 그런데 내부로 감춰야 했을 이 배열을 그만 클라이언트에 노출하는 문제가 생겼다. 그 결과 제네릭이나 매개변수화 타입이 포홤되면 알기 어려운 컴파일 경고가 발생한다. 메서드를 선언할 때 실체화 불가 타입으로 매개변수를 선언하면 컴파일러가 경고를 보낸다. 가변인수 메서드를 호출할 때도 가변 매개변수가 실체화 불가 타입으로 추론되면 경고가 발생하고, 이 매개변수화 타입의 변수가 타입이 다른 객체를 참조하면 힙 오염이 발생한다. 이런 상황에서는 컴파일러가 자동 생성한 형변환이 실패할 수 있으니, 제네릭 타입 시스템이 약속한 타입 안정성의 근간이 흔들려버린다. 메서드에 제네릭 가변인..

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

    아이템 28에서 이야기했듯 매개변수화 타입은 불공변이다. 서로 다른 타입 A와 B가 있을 때 List는 List의 하위타입도 상위타입도 아니다. List은 List의 하위 타입이 아니라는 뜻이다. 때로는 유연성을 추구해야 할 때가 있다. public class Stack{ .... } 위와 같은 제네릭 클래스가 있다. 이 Stack에 데이터를 넣기 위해서 Iterable를 제공해야 할때, E의 타입의 하위 타입도 넣을 수 있어야 한다. public void push(Iterable list, int i, int j); 어떤 선언이 더 나을까? 더 나은 이유는 무엇일까? public API라면 간단한 두 번째가 낫다. 어떤 리스트든 이 메서드에 넘기면 명시한 인덱스의 원소들을 교환해 줄 것이다. 신경 써야..

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

    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 = elemtns[--size]; elements[siz..

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

    배열과 제네릭 타입에는 중요한 차이가 두가지 있다. a가 b의 하위타입이라면 배열 a[]는 배열 b[]의 하위 타입이 된다. ( 공변 ) 그러나 제네릭은 불 공변이라 List는 List와 하위 타입도 아니고 상위 타입도 아니다. 이것만 보면 제네릭에 문제가 있는 것 같지만, 사실 문제가 있는 것은 배열 쪽이다. 다음은 문법상 허용되는 코드다. Object[] objectArray = new Long[1]; objectArray[0] = "타입"; // 런타임에 실패, ArrayStoreException 발생 하지만 다음 코드는 문법에 맞지 않는다. List ol = new ArrayList(); ol.add("타입"); // 컴파일 불가, 호환되지 않는 타입 어느쪽이든 Long 저장소에 String을 넣..

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

    제네릭을 사용하기 시작하면 수많은 컴파일러 경고가 생긴다. 비검사 경고는 컴파일러가 타입 안정성에 대한 정보를 충분히 확인하지 못했을 때 발생한다. 할 수 있는 한 모든 비검사 경고를 제거해야 한다. 비검사 경고는 런타임에 ClassCastException을 일으킬 수 있는 잠재적 가능성을 뜻한다. 타입 캐스팅으로 인해 런타임에 exception이 발생하면 경우에 따라 많은 소스를 훑어봐야 할 수도 있다..( 극 고통 발생 ) 경고를 제거할 수는 없지만 타입이 안전하다고 확실할 수 있다면 @SuppressWarnings("unchecked") 애너테이션을 달아 경고를 숨길 수 있다. 하지만 너무 넓은 범위에 해당 어노테이션을 사용하면 어노테이션으로 인해 문제가 숨겨질 수 있으니 가능한한 좁은 범위에 사용..