본문 바로가기

독서찰기(讀書札記)/이펙티브 자바

(73)
[아이템 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; //..
[아이템 28] 배열보다는 리스트를 사용하라 [Why] 배열과 제네릭 타입의 차이 첫 번째 배열은 공변(covariant; 共變)이다. Sub가 Super의 하위 타입이라면 Sub[]는 Super[]의 하위 타입 제네릭은 불공변(invariant; 不共變)이다. 서로 다른 타입 Type1과 Type2이 있을 때, List은 List의 하위 타입도, 상위 타입도 아니다. 문제는 배열 쪽에 있다. Object[] objectArray = new Long[1]; objectArray[0] = "타입이 달라 넣을 수 없다."; // ArrayStoreException을 던진다. 위 코드는 문법 상 허용되지만, 런타임에 에러를 발생시킨다. List ol = new ArrayList(); // 호환되지 않는 타입이다. ol.add("타입이 달라 넣을 수 없다..
[아이템 27] 비검사 경고를 제거하라 [Why] 할 수 있는 한 모든 비검사 경고를 제거하라. 제네릭을 사용하기 시작하면 수많은 컴파일러 경고를 보게 될 것이다. 모두 제거한다면 그 코드는 타입 안정성이 보장된다! [How] 경고를 제거할 수는 없지만 타입 안전하다고 확신할 수 있다면 @SuppressWarnings("unchecked") 애너테이션을 달아 경고를 숨기자. 경고없이 컴파일될 수는 있지만, 런타임에 여전히 ClassCastException을 던질 수 있다. 경고를 무시해도 안전한 이유를 항상 주석으로 남겨야 한다. 한편, 안전하다고 검증된 비검사 경고를 숨기지 않고 그대로 두면, 진짜 문제를 알리는 새로운 경고가 나와도 파묻힐 수 있다. @SuppressWarnings 애너테이션은 항상 가능한 한 좁은 범위에 적용하자. 심각한..
[아이템 26] 로 타입(raw type)은 사용하지 말라 용어 정리 제네릭 클래스/제네릭 인터페이스: 클래스/인터페이스 선언에 타입 매개변수(type parameter)가 쓰인 것 제네릭 타입(generic type): 제네릭 클래스와 제네릭 인터페이스를 통틀어 칭함 매개변수화 타입(parameterized type): e.g.) List은 원소의 타입이 String인 리스트를 뜻하는 매개변수화 타입 여기서 String이 정규(formal) 타입 매개변수 E에 해당하는 실제(actual) 타입 매개변수 로 타입(raw type): 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때 e.g.) List의 로 타입은 List 자바에 제네릭 기능이 생기기 전 코드와 호환되도록 하기 위한 궁여지책 한글 용어 영문 용어 예 아이템 매개변수화 타입 parameteri..
[아이템 25] 톱레벨 클래스는 한 파일에 하나만 담으라 [Why] class Utensil { static final String NAME = "pan"; } class Dessert { static final String NAME = "cake"; } // 두 클래스가 한 파일(Utensil.java)에 정의되었다. public class Main { public static void main(String[] args) { System.out.println(Utensil.NAME + Dessert.NAME); } } Main을 실행하면 pancake를 출력한다. class Utensil { static final String NAME = "pot"; } class Dessert { static final String NAME = "pie"; } // 두 클..