본문 바로가기

독서찰기(讀書札記)

(90)
[아이템 33] 타입 안전 이종 컨테이너를 고려하라 [Why] 제네릭을 쓸 때 매개변수화되는 대상은 원소가 아닌 컨테이너 자신이다. 따라서 하나의 컨테이너에서 매개변수화할 수 있는 타입의 수가 제한된다. 예컨대, Set에는 원소의 타입을 뜻하는 단 하나의 타입 매개변수만 있으면 되며, Map에는 키와 값을 뜻하는 2개만 필요한 식이다. 하지만 더 유연한 수단이 필요할 때도 종종 있다. 예컨대 데이터베이스의 행(row)은 임의 개수의 열(column)을 가질 수 있는데, 모든 열을 타입 안전하게 이용한다면 좋을 것이다. Map map1 = new HashMap(); // ValueType이 한번 고정되면 ValueType만 넣어야 함 Map map2 = new HashMap(); // Object에는 아무거나 들어갈 수 있으나 타입 안전하지 않음. map2..
[아이템 32] 제네릭과 가변인수를 함께 쓸 때는 신중하라 [Why] 가변인수는 메서드에 넘기는 인수의 개수를 클라이언트가 조절할 수 있게 해주는데, 구현 방식에 허점이 있다. 가변인수 메서드를 호출하면 가변인수를 담기 위한 배열이 자동으로 하나 만들어진다. 그런데 내부로 감춰야 했을 이 배열을 그만 클라이언트에 노출하는 문제가 생겼다. 그 결과 varargs 매개변수에 제네릭이나 매개변수화 타입이 포함되면 알기 어려운 컴파일 경고가 발생한다. static void dangerous(List... stringLists) { List intList = List.of(42); Object[] objects = stringLists; objects[0] = intList; // 힙 오염 발생 String s = stringLists[0].get(0); // Class..
[아이템 31] 한정적 와일드카드를 사용해 API 유연성을 높이라 유연성을 극대화하려면 원소의 생산자나 소비자용 입력 매개변수에 와일드카드 타입을 사용하라. [Why] 매개변수화 타입은 불공변(invariant)이다. (아이템28) 서로 다른 타입 Type1과 Type2가 있을 때 List은 List의 하위 타입도 상위 타입도 아니다. List은 List의 하위 타입이 아니라는 뜻인데, 곰곰이 따져보면 사실 이쪽이 말이 된다. List은 List가 하는 일을 제대로 수행하지 못하니 하위 타입이 될 수 없다. (리스코프 치환 원칙에 어긋난다. 아이템10) 하지만 때론 불공변 방식보다 유연한 무언가가 필요하다. public void pushAll(Iterable src) { for (E e : src) { push(e); } } 일련의 원소를 스택에 넣는 메서드이다. 이 ..
[아이템 30] 이왕이면 제네릭 메서드로 만들라 클래스와 마찬가지로, 메서드도 제네릭으로 만들 수 있다. 매개변수화 타입을 받는 정적 유틸리티 메서드는 보통 제네릭이다. [How] 제네릭 메서드 만드는 법 메서드 선언에서의 원소 타입을 타입 매개변수로 명시하고, 메서드 안에서도 이 타입 매개변수만 사용하게 수정하면 된다. 타입 매개변수들을 선언하는 타입 매개변수 목록은 메서드의 제한자와 반환 타입 사이에 온다. public static Set union(Set s1, Set s2) { Set result = new HashSet(s1); result.addAll(s2); return result; } union 메서드는 집합 3개(입력 2개, 반환 1개)의 타입이 모두 같아야 한다. (하위 타입도 안 됨. 완전히 타입이 같아야 함!) 이를 한정적 와일..
[아이템 29] 이왕이면 제네릭 타입으로 만들라 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; //..
4장 주석 사실상 주석은 기껏해야 필요악이다. 우리는 코드로 의도를 표현하지 못해, 그러니까 ‘실패’를 만회하기 위해 주석을 사용한다. 주석은 언제나 실패를 의미한다. 프로그래머들이 주석을 유지하고 보수하기란 현실적으로 불가능하다. 코드는 변화하고 진화하지만, 불행하게도 주석이 언제나 코드를 따라가지는 못한다. 우리는 (간혹 필요할지라도) 주석을 가능한 줄이도록 꾸준히 노력해야 한다. 주석은 나쁜 코드를 보완하지 못한다 자신이 저지른 난장판을 주석으로 설명하려 애쓰는 대신에 그 난장판을 깨끗이 치우는 데 시간을 보내라! 코드로 의도를 표현하라! 코드만으로 의도를 설명하기 어려운 경우가 존재한다는 생각은 분명히 잘못된 생각이다. 나쁜 예) // 직원이 복지 혜택을 받을 자격이 있는지 검사한다. if ((employe..
3장 함수 작게 만들어라! 함수에서 들여쓰기 수준은 1단이나 2단을 넘어서면 안 된다. 한 가지만 해라! 함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다. 그 한 가지만을 해야 한다. 단계가 나눠져 있더라도, 지정된 함수 이름 아래에서 추상화 수준이 하나라면 한 가지이다. 단순히 다른 표현이 아니라 의미 있는 이름으로 다른 함수를 추출할 수 있다면 그 함수는 여러 작업을 하는 중이다. 함수 당 추상화 수준은 하나로! 한 함수 내에 추상화 수준을 섞으면 특정 표현이 근본 개념인지 아니면 세부사항인지 구분하기 어려워짐 내려가기 규칙 한 함수 다음에는 추상화 수준이 한 단계 낮은 함수가 오게 한다. 즉, 위에서 아래로 프로그램을 읽으면 함수 추상화 수준이 한 번에 한 단계씩 낮아진다. Switch 문 본질적으..
2장 의미 있는 이름 의도를 분명히 밝혀라 변수, 함수, 클래스의 이름이 포함하고 있어야 할 정보 존재 이유 수행 기능 사용 방법 예시 int d; bad int daysSinceCreation; good 그릇된 정보를 피하라 목록을 불러오더라도 List 자료형을 사용하는 것이 아니면 이름에 List를 포함시키지 말 것 목록을 List를 이용해 담아오더라도 이름에 List는 안 쓰는게 좋음 흡사한 이름 쓰지 말 것 XYZControllerForEfficientHandlingOfStrings XYZControllerForEfficientStorageOfStrings ‘소문자 L, 대문자 O’ ‘l’은 숫자 1과 헷갈리고 O는 숫자 0과 헷갈림 의미 있게 구분하라 컴파일러/인터프리터만 통과하면 된다는 식으로 작명 X 연속적인 숫..