작성자: 프람
작성 일시: 2024.05.09
내용: Effective Java 3/E 아이템-28
인사
안녕하세요.👋 이번주도 역시나 여러모로 너무 바쁜 나날들을 보내고 있네요.
그래도 할건 해야겠죠??
이번 주제는 '아이템28-배열보다는 리스트를 사용하라'입니다.
퐈이팅 넘치게 시작해보겠씁니다 💪
배열 VS 리스트
우선 배열과 리스트의 차이점으로 시작해보겠습니다.
- 배열은 공변(covariant)이다/ 제네릭은 불공변(invariant)이다
- 배열은 실체화(reify) 된다/ 제넥릭은 소거(erasure)된다
- 배열은 제네릭 타입, 매개변수화 타입, 타입 매개변수로 사용할 수 없다
이렇게 글로 정리하니 너무 어려워 보이지만, 우린 개발자니까~~
코드로 보면 쉽게 이해할 수 있습니다.
1. 배열은 공변(covariant)/ 제네릭은 불공변(invariant)
코드를 보면 딱 감이 오시죠?
Object[] ObjectArray = new Long[1];
배열은 할당 받은 타입에 의해, 런타임에 배열의 타입이 시시각각 바뀔 수 있다는 것입니다.
때문에, 그 아랫 줄에서 런타임에러로 'ArrayStoreException'을 던져줍니다.
objectArray[0] = "타입이 달라 넣을 수 없다";
즉, 공변은 런타입에 하위 타입으로 변경될 수 있다는 뜻입니다.
반대로 불공변은 컴파일 타입에 확실히 타입이 지정되고 더 이상 바뀔 수 없다는 뜻이겠죠?
그렇기 때문에 위의 코드는 아무런 문제없이 잘 돌아가죠!
2. 배열은 실사화 / 제네릭은 소거
이것도 딱히 어려운것은 아닌데 '아이템 26. 로 타입은 사용하지 말라'를 참고하면 이해하기 더 편해요.
로 타입은 사용하지 말라
작성자: 프람작성 일시: 2024_05_06내용: Effective java 3/E 5장 아이템26 이펙티브 자바 Effective Java 3/E - 예스24자바 플랫폼 모범 사례 완벽 가이드 - Java 7, 8, 9 대응자바 6 출시 직후 출간된 『이펙티브
fram-tech.tistory.com
짧게 여기서 다시 설명하자면,
제네릭은 하위 호환성을 위해 컴파일러가 컴파일 타임에 제네릭 타입을 Object 타입으로 형 변환 시켜줍니다.
이것을 소거라고 하는 것이죠.
반면, 배열은 런타임까지 본인의 타입을 스스로 알고 있다는 것이에요.
이러한 내부 원리를 알게되었으니 다시 1번의 내용 공변성과 불공변성이 이해가 더 쉽게 갈거에요.
3. 배열은 제네릭 타입, 매개변수화 타입, 타입 매개변수로 사용할 수 없다
즉 , 아래 표와 같은 문법은 지원하지 않습니다.
이름 | 예시 코드 |
제네릭 타입 | new List<E>[] |
매개변수화 타입 | new List<String>[] |
타입 매개변수 | new E[] |
코드로도 살펴보죠.
만약, (1)이 허용된다고 가정해봅시다.
좀 복잡해보이겠지만,
결국은 배열은 공변(covariant)/ 제네릭은 불공변(invariant)에서의 예제와 같이 ArrayStoreException을 발생시킵니다.
그럼 아까 '2번 배열은 실사화한다'라는 설명이 틀린거 아닌가요?
'(List<String>)타입의 배열인데 왜 배열을 컴파일 타입에 잡아줘?'라고 생각을 할 수도 있는데요.
제네릭이 주는 가장 큰 이점 중 하나가 무엇인지 아시나요?
바로 컴파일 타입에 컴파일러가 타입을 확인해주기 때문에 타입 안정성을 보장해주며 코드를 가독성있게 쓸 수있다는 것인데요.
위 표와 같은 문법(Syntax)들이 지원된다면, 제네릭이 주는 장점이 모두 사라지겠죠?
그래서 자바에서는 E, List<E>, List<String>은 별도로 실체화 불가 타입(non-reifiable type)으로 분류합니다.
(즉, 런타임이 컴파일타임보다 더 적은 정보를 가지고 있음을 뜻합니다)
실사화 불가 타입의 불편함
곰곰이 생각해보니new E[] 또는 new Array<Object>[]이 지원된다면, 더 편하지 않을까? 라는 생각이 들지 않으신가요?
또는, 제넥릭 타입과 가변인수 메서드(vargargs method)[1]를 함께 쓰면 해석하기 어려운 경고 메세지를 받을 수도 있습니다.
이 문제는 @SafeVarargs 애너테이션으로 해결이 됩니다. 이는 추후에 더 하세하게 다루겠습니다.
각 불편함에 대해 코드로 더 자세하게 알아 보겠습니다.
1. 배열을 제네릭으로 만들 수 없을 때의 귀찮음
우선 다면체 주사위 클래스를 배열로 간단하게 만들어 보겠습니다
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)];
}
}
제네릭을 구현하지 않은 위 Chooser 클래스는 형변환을 사용할 때마다 번거롭게 해줘야합니다.
또 잘못된 형변환을 시도할 시 런타임에서야 예외를 발생시킬 것이죠.
이러한 문제를 해결하기 위해 우리는 하나의 꾀를 내어 보기로 했습니다.
public class Chooser<T> {
private final T[] choiceArray;
public Chooser(Collection<T> choices) {
choiceArray = choices.toArray();
}
public T choose() {
Random rnd = ThreadLocalRandom.current();
return chioceArray[rnd.nextInt(choiceArray.length)];
}
}
잘 작동할까요?
앞서 말씀드린것과 같이 T[]는 실사화 불가 타입에 해당하기 컴파일오류를 뿜어주고 있네요.
실행시켜 더 자세한 오류 메세지를 살펴 봅시다.
T의 타입이 불분명하니 형변환이 런타임에도 안전한지 보장할 수 없다는 메세지입니다.😭
힝.. 그럼 어떻게 해야할까요??
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()));
}
}
배열은 탐색 속도가 O(1)이지만 List는 O(n)입니다. 하지만 타입 안정성을 위해서 이정도 속도는 충분히 트레이드 오프를 할만하다고 할 수 있죠!!
2. 가변인수 메서드를 함께 쓰면 해석하기 어려운 경고 메세지를 받을 수 있음의 귀찮음
힙 오염(heap pollution)을 야기 할 수 있는 코드 때문에 생기는 경고 메세지를 말합니다.
즉, 이것도 설명은 긴데요.
앞서 보았던 타입 안정성에 대한 경고를 뜻합니다.
public class HeapPollution {
static String firstOfFirst(List<String>... strings) {
List<Integer> ints = Collections.singletonList(42);
Object[] objects = strings;
objects[0] = ints;
return strings[0].get(0);
}
}
위 코드를 사용하면 나타는 경고는 아래와 같습니다.
완벽히 안전하다 생각이 된다면 @SafeVarargs 애노테이션을 붙여 경고를 끌 순있답니다.
선언부는 생성자와 메서드 레벨입니다.😊
@SafeVarargs
static String firstOfFirst(List<String>... strings) {
List<Integer> ints = Collections.singletonList(42);
Object[] objects = strings;
objects[0] = ints;
return strings[0].get(0);
}
Tip) Varargs method 사용
저렇게 위험한 가변인수 메서드는 저장이 일어나지 않는 경우
또는 외부로 사용한 참조를 반환하지 않는 경우만 사용하라고 합니다.
결론
배열은 공변한다는 특징이 있는데요. 이것은 런타임에 타입이 수시로 변경될 수 있음을 의미하므로 배열보다는 리스트를!
또 new List<E>[], new E[], new List<Object> 등은 실사화 불가 타입임으로 불편함이 존재한다.
이럴 때도 앵간하면 List로 해결해라.
Varargs Method 역시 힙 오염을 발생 시킬 수 있으니 사용은 자제하고, List로 대체하자
참고자료
'독서 감상문 > Effective Java' 카테고리의 다른 글
Ordinal 인덱싱 대신 EnumMap을 사용하라 (0) | 2024.05.16 |
---|---|
비트 필드 대신 EnumSet을 사용하라 (0) | 2024.05.16 |
비검사 경고를 제거하라 (0) | 2024.05.06 |
로 타입은 사용하지 말라 (0) | 2024.05.06 |
태그 달린 클래스보다는 클래스 계층구조를 활용하라 (0) | 2024.05.03 |