이펙티브 자바 Item 26,27,28 (제네릭 관련)
Item 26 (로 타입은 사용하지 마라)
로타입은 List list = new ArrayList(); 형태로 사용하는 방식이다.
매개변수화 타입은 List<Object> list = new ArrayList<>(); 방식
로타입은 뭐든지 다 들어간다.
List list = new ArrayList();
list.add("문자열");
list.add(1);
String text = (String) list.get(0);
Integer number = (Integer) list.get(1);
문자열, 숫자 가리지 않고 다 넣을 수 있다. 하지만, 타입 캐스팅해줘야하는 경우가 빈번해진다.
제네릭을 통해 타입 안정석과 표현력을 가지고 가자
로타입이 쓰고 싶을 땐?
로타입을 사용하지 않는다면, 메서드를 만들때 타입에 맞는 메서드를 여러개 구현해야하는 불편함이 있다.
private int count(Set<Object> destination, Set<String> source) {
....
return result;
}
private int count(Set<Object> destination, Set<Integer> source) {
....
return result;
}
이럴 경우 비한정적 와일드카드 타입을 사용해서 해결한다.
- 와일드 카드는 어떤 타입이든 상관없이 받을 수 있지만, 타입의 안정성을 보장해준다.
private int count(Set<?> destination, Set<?> source) {
....
return result;
}
로타입을 써도 좋은 곳
1. 클래스 리터럴에는 로타입을 해도 괜찮다. (클래스 리터럴은 매개변수화(List) 타입을 사용하지 못하기 때문이다.)
- 자바 명세에서 class리터럴에 매개변수화 타입을 사용하지 못하게 됨 (List.class, String[].class 등)
2. instanceof에 사용해도 괜찮다. (런타임에는 제네릭 타입이 지워진다. instanceof에서 리스트타입인지 알고 싶다면, 로타입이든 와일드 카드든 둘 중 하나를 사용하자)
- instanceof 연산자는 비한정적 와일드 카드 외 매개변수화 타입 적용할 수 없음
if (o instanceof Set) { // 로 타입
Set<?> s = (Set<?>) o; // 와일드카드 타입
}
Item 27 (비검사 경고를 제거하라)
1.제거할 수 있는 경고는 제거하자
2. 제거할 수 없는 경고는 숨기자
3. SuppressWarnings("uncheck") 어노테이션은 경고를 감춰준다. (함수선언, 변수선언 부분)
*SUppressWarning은 절대 클래스 전체에 적용하지마라, 메서드나 생성자에 달려있으면 지역 변수나 아주 짧은 메서드 혹은 생성자로 옮겨두자
Item 28 배열보다는 리스트를 사용하라
제네릭은 런타임에 안전하다 !
- 배열은 공변으로 존재하고, 제네릭은 불공변으로 존재한다!
- 다른 말로 배열은 실체화 가능 타입이고, 제네릭은 실체화 불가 타입이다. (E, List<E>와 같은 타입) -> List<?>, Map<?,?>은 실체화 될 수 있는 타입이다.
실체화 가능 타입은 런타임에도 타입정보를 가지고 있다.
제네릭은 런타임에 타입정보를 가지고 있지 않다. -> new List<E>[], new E[]같은 제네릭을 배열로 만드는 행위 불가능하다.
*실체화 불가 타입은 런타임에는 컴파일타임보다 타입 정보를 적게 가지는 타입(공변이 잘 안됨)
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
위와 같은 상황을 가정한다면, 5번에서 에러가 발생한다.
이런 상황은 배열을 제네릭으로 만들 수 없어 귀찮은 상황이다.
제네릭 컬렉션은 자기 자신의 배열을 반환할 수 없다(예외가 있긴함)
가변인수 메서드를 제네릭과 함께 쓸때는 알 수 업는 경고를 발생시킨다 (@SafeVarags 어노테이션으로 해결가능하다)
@SafeVarargs
public static void print(List... names) {
for (List<String> name : names) {
System.out.println(name);
}
}
- 가변인수 메서드는 호출 시 가변인수 매개변수로 담은 배열이 만들어지는데, 원소가 실체화 불가 타입이라면 경고가 발생함!! (List...names) -> ...이 뭔지알고! 넣어!
배열 대신 컬렉션을 사용하자
배열로 형변환시 발생하는 배열 생성 오류는 대부분 List<E>로 해결가능하다.
public class Chooser {
private final Object[] choiceArray;
public Chooser(final Object[] choiceArray) {
this.choiceArray = choiceArray;
}
public Object choose(){
Random random = ThreadLocalRandom.current();
return choiceArray[random.nextInt(choiceArray.length)];
}
}
- 아무타입의 배열도 다 받을 수 있게 해두었는데, 타입 에러 날 가능성이 크다 (형변환 오류)
public class ListChooser {
private final List<T> choiceList;
public ListChooser(final Collection<T> choices) {
this.choiceList = new ArrayList<>(choices);
}
public T choose(){
Random random = ThreadLocalRandom.current();
return choiceList[random.nextInt(choiceList.size())];
}
}
- 대입받는 List의 <> 구현에 따라서 변화하므로, 형변환 오류가 발생하지 않는다.
참고자료
https://javabom.tistory.com/43