만약 위에처럼 코드를 작성했다면, 컴파일러가 new HashSet<>(); 를 사용하라고 알려줌.(자바 7부터 가능)
경고를 제거할 수는 없고 타입이 안전하다고 확신할 수 있다면 @SuppressWarnings("unchecked") 어노테이션으로 제거 가능.
단 검증하지 않은 채 경고 숨기면 컴파일은 되지만, 런타임에는 여전히 오류 발생.
대신 충분히 안전한데, 안 지워주면 수많은 비검사 경고들 사이에 새로운 경고가 묻히게 됨.
@SuppressWarnings은 개별 지역변수 선언부터, 클래스 전체까지 선언할 수 있지만, 최소한으로 사용하자.
28. 배열보다는 리스트
배열과 제네릭 타입의 차이
배열은 공변, 제네릭은 불공변
Sub 가 Super 의 하위라면, Sub[] 는 Super[] 의 하위, 하지만 Type1, Type2 가 있을때 List 과 List 는 아무 관계도 아님.
Object[] objectArray = new Long[1];
obejctArray[0] = "타입이 달라서 못넣음"; // ArrayStoreException을 던짐, 이코드는 런타입 실패
List<Object> objectList = new ArrayList<Long>(); // 호환되지 않는 타입
objectList.add("타입이 달라 넣을 수 없다"); // 아예 컴파일 자체도 안됨
배열은 실체화가 됨
배열은 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인함.
Long 배열에 String을 넣으려고 하면 ArrayStoreException이 발생
하지만 제네릭은 런타임 시에 타입이 소거됨. 타입 정보를 컴파일 시점에만 인지하는데, 이건 제네릭 지원 전의 코드도 사용하기 위함이다.
제네릭 배열을 만들지 못하게 막은 이유
List<String>[] stringLists = new List<String>[1]; // (1) 만약 제너릭 베열이 된다고 가정
List<Integer> intList = List.of(42); // (2)
Object[] objects = stringLists; // (3)
objects[0] = intList; // (4)
String s = stringLists[0].get(0); // (5)
(2)는 원소가 하나인 List<Integer>를 생성
(3)은 (1)에서 생성한 List<String>의 배열을 Object 배열에 할당
배열은 공변이니 문제가 없음
(4)는 (2)에서 생성한 List<Integer>의 인스턴스를 Object 배열의 첫 원소로 저장
제너릭은 소거 방식으로 구현되어서 역시 성공함.
List<Integer> 인스턴스의 타입은 단순히 List 가 되고, List<Ingeter>[] 인스턴스 타입은 List[] 가 되어서 아직까진 문제 X
(5)에서 List<String> 만 담기로 선언했던 stringLists에 List<Ingeter 가 담겨있음.
첫 번째 원소를 꺼내서 String 으로 형 변환할때 ClassCastException이 발생함.
이런 이유를 볼때 제너릭 배열이 생성되지 않도록 사전에 컴파일 오류가 발생되어야 함.
E, List<E>, List<String> 이런걸 실체화 불가 타입(non-reifiable type) 이라고 함. 제너릭의 소거 특성으로 인해 실체화가 되지 않아 런타임 시에 컴파일 타임보다 타입 정보를 적게 갖음.
@SafeVarargs 를 사용하면 실체화 불가타입 경고를 막을 수 잇음.
29. 이왕이면 제네릭 타입
아이템 7에서 다룬 예시를 제너릭 타입으로 적용해본다면
// Object 기반으로 구현된 스택
public class Stack { // Stack<E> 로 변경
private Object[] elements; // E[] elements 로 변경
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
// new E[DEFAULT_INITIAL_CAPACITY]; 로 변경
}
public void push(Object e) { // push(E e) 로 변경
ensureCapacity();
elements[size++] = e;
}
public Object pop() { // E pop() 으로 변경
if (size ==0) {
throw new EmptyStackException();
}
Object result = elements[--size]; // E 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);
}
}
주석의 내용처럼 변경이 가능.
하지만 경고가 몇개 발생
E와 같이 실체화 불가 타입은 배열로 만들 수 없으므로
(E[]) new Object[DEFAULT_INITIAL_CAPACITY]; 로 변경
또는 @SuppressWarning 를 사용하는 방법.
30. 이왕이면 제너릭 메서드
클래스와 마찬가지로 메서드도 제너릭으로 가능
Collections의 binarySearch, sort 모두 제너릭 메서드임.
public static Set union(Set s1, Set s2) {
Set result = new HashSet(s1);
result.addAll(s2);
return result;
}
여기서 new HashSet(s1), result.addAll(s2) 두군대에서 에러가 발생함.
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
따라서 메서드를 만들때도 제너릭 메서드로 만들면 경고없이 컴파일 되고, 안전하고, 쓰기 쉬움.
제너릭 싱글턴 팩터리
때로 불변객체를 여러 타입으로 활용할 수 있게 만들어야 할 때가 잇음.
제너릭은 런타임할때 타입 정보가 소거되므로 하나의 객체를 어떤 타입으로든 매개변수화 할 수 있음
하지만 매번 매개변수에 맞게 타입을 바꿔주려면 정적 팩터리를 만들어야함,
이걸 제너릭 싱글턴 팩터리라고 한다. 예시로 Collections.resversOrder, Collections.emptySet 이 존재.
재귀적 타입 한정
자기 자신이 들어간 표현식을 사용해서 타입 매개변ㄴ수의 허용 범위를 한정
주로 타입의 자연적 순서를 정하는 Comparable 인터페이스와 함께 쓰임
public interface Comparable<T> {
int compareTo(T o);
}
여기서 매개변수 T는 구현한 타입이 비교할 수 있는 원소를 정의(거의 모든타입은 자신과 같은 타입의 원소만 비교할 수 있음)
Stirng은 Compareable 을 구현하고, Integer는 Compareable 를 구현함
따라서 컬렉션에 담긴 모든 원소가 상호 비교가 가능해야함
// 재귀적 타입 한정을 이용해 상호 비교할 수 있음을 표현
public static <E extends Comparable<E>> E max(Collection<E> c);
31. 한정적 와일드카드으로 api 유연성 향상
제네릭은 불공변, List<Type1> 과 List<Type2>는 연관이 없음.
하지만 이런 불공변 방식부다 유연한게 필요
public void pushAll(Iterable<E> src) {
for (E e : src) {
push(e);
}
}
컴파일은 정상수행 되지만, 여기서 E대신 Number를 사용하고, 메서드에 Integer 를 넣게되면 오류메세지 발생
매개변수화 타입이 불공변이기 때문에 Iterable<Integer> cannot be converted to Iterable<Number> 발생
자바엔 이걸 해결하기위해 와일드카드타입을 제공
Iterable<? extends E> 의 뜻은 E의 Iterable 이 아니라 E의 하위타입의 Iterable 이어야함.
public void pushAll(Iterable<? extends E> src) {
for (E e : src) {
push(e);
}
}
Stack의 pop 도 마찬가지로, popAll 을 짤때 E의 Collection 이 아니라 E의 상위 타입의 Collection 의 의미로 짜야한다.
public void popAll(Collection<? super E> dst) {
while(!isEmpty()) {
dst.add(pop());
}
}
PECS (producer-extends, consumer-super) 공식을 외우면 좋다.
5장 제네릭
제네릭은 자바 5부터 사용 가능
제네릭 지원하기 전에는 컬렉션에서 객체를 꺼낼 때 마다 형변환이 필요
그래서 human fault로 엉뚱한 타입의 객체를 넣어두면 런타입 오류가 발생하곤 했음
제너릭은 담을 수 있는 타입을 컴파일러에게 알려줌
컴파일러는 알아서 현변환 코드를 추가, 타입이 다른 객체를 추가하려고 하면 컴파일 과정에서 차단해서 방어
꼭 컬렉션이 아니더라도, 장점이 있으나, 코드가 복잡해진다는 단점이 따라옴
5장에서는 장점 최대화, 단점 최소화로 제네릭을 사용해보장
26. 로 타입 사용 ㄴㄴ
List<String>
에서List<String>
: 제너릭타입String
: 매개변수화 타입List
: 로타입로타입은 타입 선언에서 제네릭 타입 정보가 전부 지워진 것처럼 동작하는데, 제너릭이 도래하기 전 코드와 호환하기 위함이다.
제네릭을 지원하기 전에는 컬렉션을 아래처럼 사용함
이 코드를 사용하면 stamps 타입이 아닌, Coin 타입을 넣어도 오류없이 컴파일되고, 실행이 되고, 꺼내기 전까지 오류를 알아채지 못함.
이렇게 되면, 런타임에 문제를 겪는 코드와 원인을 제공한 코드가 물리적으로 떨어져 있을 가능성이 크다.
ClassCastException 이 발생하면, stamps 에 동전을 넣는 지점을 찾기 위해 코드 전체를 훑어봐야함
제너릭을 활용하면 이 아래처럼 짤 수 있고, 주석의 도움이 아닌 타입 선언 자체에 정보가 있음
이렇게하면 컴파일러는 stamps 안에는
Stamp
타입만 들어가야하는것을 인지한다.억지같지만
BigDecimal
컬렉션에BigInteger
넣는 실수는 현업에서 종종 일어남List
는 사용하면 안되지만,List<Object>
는 사용해도 됨List
를 받는 메서드에List<String>
을 넘길 수 있지만,List<Object>
는 넘길 수 없음위 코드 컴파일은 되지만, 로타입인 List를 사용해서 경고를 받음
이거 그대로 실행하면, 결과를 형변환할때
ClassCastException
을 던짐. (Integer를 String으로 변환시도)근데 위의 코드를 아래처럼 바꾸면 컴파일조차 되지 않는다.
만약 2개의 Set을 받아서 공통 원소를 반환하는 코드를 짜는 함수를 짜보면
잘못된 예시
동작은 하지만 로타입을 사용해서 안전하지는 않음.
따라서 비한정 와일드카드 타입(unbouned wildcard type) 을 대신 사용하는게 좋음
좋은예
물음표를 써서 범용적인 매개변수화 Set 타입을 선언.
로타입과 ?의 차이는 로타입은 타입 불변식을 훼손하기 쉬움.
?는 null 외에 다른 원소를 넣으려 하면 컴파일 할 때 오류 발생.
하지만 예외는 있음 classs 리터럴에는 로 타입을 사용해야한다.
자바 명세에 class 리터럴에는 매개변수화 타입을 사용하지 못하게 했음.
instanceof 연산자도 마찬가지로 로타입을 사용해야함.
이렇게 o 타입이 Set 임을 확인하고, Set<?> 로 형변환 하면 컴파일러 경고가 뜨지 않는다.
27. 비검사 경고를 제거
new HashSet<>();
를 사용하라고 알려줌.(자바 7부터 가능)28. 배열보다는 리스트
List<Integer>
를 생성List<String>
의 배열을 Object 배열에 할당List<Integer>
의 인스턴스를 Object 배열의 첫 원소로 저장List<Integer>
인스턴스의 타입은 단순히 List 가 되고,List<Ingeter>[]
인스턴스 타입은 List[] 가 되어서 아직까진 문제 XList<String>
만 담기로 선언했던 stringLists에List<Ingeter
가 담겨있음.E
,List<E>
,List<String>
이런걸 실체화 불가 타입(non-reifiable type) 이라고 함. 제너릭의 소거 특성으로 인해 실체화가 되지 않아 런타임 시에 컴파일 타임보다 타입 정보를 적게 갖음.29. 이왕이면 제네릭 타입
아이템 7에서 다룬 예시를 제너릭 타입으로 적용해본다면
(E[]) new Object[DEFAULT_INITIAL_CAPACITY];
로 변경30. 이왕이면 제너릭 메서드
제너릭 싱글턴 팩터리
재귀적 타입 한정
31. 한정적 와일드카드으로 api 유연성 향상
제네릭은 불공변,
List<Type1>
과List<Type2>
는 연관이 없음.하지만 이런 불공변 방식부다 유연한게 필요
컴파일은 정상수행 되지만, 여기서 E대신
Number
를 사용하고, 메서드에Integer
를 넣게되면 오류메세지 발생매개변수화 타입이 불공변이기 때문에
Iterable<Integer> cannot be converted to Iterable<Number>
발생자바엔 이걸 해결하기위해 와일드카드타입을 제공
Iterable<? extends E>
의 뜻은E의 Iterable
이 아니라E의 하위타입의 Iterable
이어야함.E의 Collection
이 아니라E의 상위 타입의 Collection
의 의미로 짜야한다.PECS (producer-extends, consumer-super)
공식을 외우면 좋다.union
에 적용을 시키면 아래와 같음Set<E>
라는점32. 제네릭과 가변인수를 함께쓸때는 신중하게
ClassCastException
이 발생33. 타입 안전 이종 컨테이너를 고려
Set<E>, Map<K, V>
과,ThreadLocal<T>, AtomicReference<T>
등에도 많이 사용됨class 리터럴의 타입은 Class가 아니라
Class<T>
String.class
의 타입은Class<String>
public void putFavorite(Class type, T instance) {
favorites.put(Objects.requireNonNull(type), instance);
}
public T getFavorite(Class type) {
return type.cast(favorites.get(type));
}
이렇게 짜면
type.cast(instance)
방식으로 바꿔주는 동적 형변환으로 안정성 확보 가능String
이나String[]
이건 저장 가능하지만,List<String>
이건 불가.List<String>
이거랑List<Integer>
이거 둘다List.class
로 공유하기때문임슈퍼 타입 토큰
이 있음new TypeRef<List<String>>
이게 슈퍼타입 토큰