5장 제네릭
아이템 26. 로 타입은 사용하지 말라
아이템 27. 비검사 경고를 제거하라
아이템 28. 배열보다는 리스트를 사용하라
아이템 29. 이왕이면 제네릭 타입으로 만들라
아이템 30. 이왕이면 제네릭 메서드로 만들라
아이템 31. 한정적 와일드카드를 사용해 API 유연성을 높이라
아이템 32. 제네릭과 가변인수를 함께 쓸 때는 신중하라
아이템 33. 타입 안전 이종 컨테이너를 고려하라
배열 VS 제네릭 (공변)
배열은 공변(covariant)이다. 즉, Sub 클래스가 Super 라는 클래스의 하위 타입이라면, 배열 Sub[]은 배열 Super[]의 하위 타입이 된다. 이것을 공변이라고 한다. 하지만 제네릭 불공변(invariant)이다. 서로 다른 Type1과 Type2가 있을 때, List<Type1>은 List<Type2>의 상위 타입도 하위 타입도 아니다.??
class DelayQueue<E extends Delayed> implements BlockingQueue<E>recursive type bound
자기 자신이 들어간 표현식을 사용하ㄱ여 타입 매개변수 허용범위 결정.
// 재귀적 타입 한정을 이용해 상호 비교할 수 있음을 표현
public static <E extends Comparable<E>> E max(Collection<E> c);
생각해보니 이 말의 뜻은 아 저기 들어갈수 있는 E 타입 매개변수는 그 클래스가 Comparable를 구핸해야되구나! 를 의미할수도 있을듯.
public interface Comparable<T> {
/**
* Compares this object with the specified object for order. Returns a
* negative integer, zero, or a positive integer as this object is less
* than, equal to, or greater than the specified object.
*
* <p>The implementor must ensure
* {@code sgn(x.compareTo(y)) == -sgn(y.compareTo(x))}
* for all {@code x} and {@code y}. (This
* implies that {@code x.compareTo(y)} must throw an exception iff
* {@code y.compareTo(x)} throws an exception.)
*
* <p>The implementor must also ensure that the relation is transitive:
* {@code (x.compareTo(y) > 0 && y.compareTo(z) > 0)} implies
* {@code x.compareTo(z) > 0}.
*
* <p>Finally, the implementor must ensure that {@code x.compareTo(y)==0}
* implies that {@code sgn(x.compareTo(z)) == sgn(y.compareTo(z))}, for
* all {@code z}.
*
* <p>It is strongly recommended, but <i>not</i> strictly required that
* {@code (x.compareTo(y)==0) == (x.equals(y))}. Generally speaking, any
* class that implements the {@code Comparable} interface and violates
* this condition should clearly indicate this fact. The recommended
* language is "Note: this class has a natural ordering that is
* inconsistent with equals."
*
* <p>In the foregoing description, the notation
* {@code sgn(}<i>expression</i>{@code )} designates the mathematical
* <i>signum</i> function, which is defined to return one of {@code -1},
* {@code 0}, or {@code 1} according to whether the value of
* <i>expression</i> is negative, zero, or positive, respectively.
*
* @param o the object to be compared.
* @return a negative integer, zero, or a positive integer as this object
* is less than, equal to, or greater than the specified object.
*
* @throws NullPointerException if the specified object is null
* @throws ClassCastException if the specified object's type prevents it
* from being compared to this object.
*/
public int compareTo(T o);
}
생산자일때
// class Integer extends Number ...
public void pushAll(Iterable<? extends E> src) {
for (E e : src) {
push(e);
}
}
****
소비자 일떄(중요, 내 정보를 인자로 받은 컬렉션에 넣으려면, 내것이 일반적이여야하기 때문에 super를 썼다는 것이 중요)
// E의 상위 타입의 Collection이어야 한다.
public void popAll(Collection<? super E> dst) {
while(!isEmpty()) {
dst.add(pop());
}
}
List<?>
public class Test {
public static <E> void main(String[] args) {
List<?> list2 = new ArrayList<Integer>();
list1.add(1); // 컴파일 에러
}
}
List<?> 이것에는 값을 추가로 넣을수 없다. 이미 참조하고 있는 것은 상관 없
이런 상황에 응용 가능
public static void printList(List<?> list) {
for (Object elem: list)
System.out.print(elem + " ");
System.out.println();
}
다만, 개발자는 어떤 타입의 객체를 담은 리스트가 들어올지도 모르는 상황에서 특정 타입의 객체를 들어온 리스트에 넣어줄 수는 없다. 그래서 null 값만 넣어줄 수 있는 것 같습니다.
import java.util.ArrayList;
import java.util.List;
public class Example {
static void dangerous(List<String>... stringLists) {
List<Integer> intList = List.of(42);
Object[] objects = stringLists;
objects[0] = intList; // 힙 오염 발생
String s = stringLists[0].get(0); // ClassCastException
}
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("Hi there");
dangerous(stringList);
}
}
https://inpa.tistory.com/entry/JAVA-☕-제네릭-힙-오염-Heap-Pollution-이란
하지만 제네릭이나 매개변수화 타입의 varargs 매개변수를 받는 메서드가 실무에서 매우 유용하기 때문에, 위의 예제처럼 제네릭 varargs 매개변수를 받는 메서드를 선언할 수 있도록 했다. 대표적으로 아래와 같이 Arrays.list(T... a), EnumSet.of(E first, E... set)과 같은 메서드가 있다.
`// Arrays.Java
@SafeVarargs
@SuppressWarnings("varargs")
public static List asList(T... a) {
return new ArrayList<>(a);
}
// EnumSet.java
@SafeVarargs
public static <E extends Enum> EnumSet of(E first, E... rest) {
EnumSet result = noneOf(first.getDeclaringClass());
result.add(first);
for (E e : rest)
result.add(e);
return result;
}`
`public class Favorites {
// 제네릭을 중첩해서 썼으므로 class 리터럴이면 뭐든 넣을 수 있다.
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));
}
public static void main(String[] args) {
Favorites f = new Favorites();
f.putFavorite(String.class, "Java");
f.putFavorite(Class.class, Favorites.class);
String favoriteString = f.getFavorite(String.class);
Class<?> favoriteClass = f.getFavorite(Class.class);
// 출력 결과: Java Favorites
System.out.printf("%s %s%n", favoriteString, favoriteClass.getName());
}
}`
위 Favorites 클래스는 타입 안전하다. String을 요청했을 때 Integer를 반환하는 등의 예외가 발생하지 않는다. 하지만 이 구현에도 단점은 존재한다. 먼저, 넣을 때 잘못 넣으면 오류가 발생할 수 있습니다.
f.putFavorite((Class)Integer.class, "This is not integer !!!"); Integer notInteger = f.getFavorite(Integer.class); // ClassCastException
이것을 해결하기 위한 방법으로
public <T> void putFavorite(Class<T> type, T instance) {
favorites.put(Objects.requireNonNull(type), Objects.requireNonNull(type.cast(instance)));
}
또한 실체화가 불가능한 타입은 넣을 수 없다. 그러니까, String이나 String[]은 저장할 수 있지만, List<String>은 저장할 수 없다. 우회하기 위한 방법으로는 슈퍼 타입 토큰을 사용할 수 있다. 슈퍼 타입을 토큰으로 사용한다는 뜻이다. 스프링 프레임워크에서는 이를 클래스로 미리 구현해놓았다.
List<String> pets = Arrays.asList("강아지", "고양이"); f.putFavorite(new TypeRef<List<String>>(){}, pets); List<String> list = f.getFavorite(new TypeRef<List<String>>(){});