// 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-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)
// 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)));
}
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(); // 도달할 수 없다
}
// 배열 대신 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));
}