티스토리 뷰
728x90
반응형
배열보다는 리스트를 사용하라
- 배열과 제네릭 타입에는 중요한 차이가 2가지 있습니다.
💡 차이점 1) 배열은 공변이고, 제네릭은 불공변입니다.
- 배열은 공변입니다. 공변이란 예를들어 Sub가 Super의 하위 타입이라면 배열 Sub[]는 배열 Super[]의 하위 타입입니다.(공변 = 함께 변한다.)
- 반면 제네릭은 불공변입니다. 즉 서로 다른 타입 Type1, Type2가 있을 때 List<Type1>은 List<Type2>의 하위 타입도 상위 타입도 아닙니다. 그냥 서로 다른 타입입니다.
- 하지만 문제가 되는 것은 배열입니다.
🧨 예제 코드
- Case 1같은 경우는 문법적으로는 허용되며 컴파일 단계에서 문제를 발견하지 못하고 런타임시에 예외가 발생합니다.
- Case 2같은 경우는 컴파일 에러가 발생하여 개발자가 바로 알 수 있습니다.
// Case 1
Object[] objects = new Long[1];
objects[0] = "문자열을 넣을 수 없습니다."; // ArrayStoreException 발생(런타입 에러)
// Case 2
List<Object> list = new ArrayList<Long>(); // 컴파일 에러 발생
list.add("문자열을 넣을 수 없습니다.");
💡 차이점 2) 배열은 실체화, 제네릭은 소거
- 배열은 실체화가 가능합니다. 무슨 뜻이냐면, 배열은 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인합니다. 그래서 위의 예제에서 Long 배열에 String을 넣으려 할 때 담기로한 원소의 타입을 확인할 때 다른 걸 확인해서 예외를 발생시킵니다.
- 하지만 제네릭은 타입 정보가 런타임에는 소거됩니다. 원소 타입을 컴파일 타임에만 검사하며 런타임에는 알 수 조차 없다는 의미입니다.
💡 배열은 제네릭을 사용할 수 없습니다.
- 배열은 제네릭 타입, 매개변수화 타입, 타입 매개변수를 사용할 수 없습니다.
List<E>[], new List<String>[], new E[] // 컴파일 에러 발생
💡 제네릭은 배열을 만들지 못합니다.
- 그 이유는 타입이 안전하지 않기 때문입니다. 이를 허용한다면 커파일러가 자동 생성한 형변환 코드에서 런타임에 ClassCastException이 발생할 수 있습니다.
- 만약 아래의 코드에서 (1) 중 new List<String>[1]이라는 제네릭 배열이 허용된다면 아래와 같이 진행됩니다.
(1) - List<String>[] stringLists = new List<String>[1];
(2) - List<Integer> intList = List.of(42);
(3) - Object[] objects = stringLists;
(4) - objects[0] = intList;
(5) - String s = stringLists[0].get(0);
- (1)에서 stringLists라는 List<String> 타입의 제네릭 배열이 생성됩니다.
- (2)에서 42라는 원소를 가진 Integer 타입 매개변수의 intList가 선언됩니다.
- (3)에서 stringLists 변수를 Object 배열인 objects에 할당합니다.
- (4)에서 intList를 Object 배열의 0번 index에 할당합니다. 런타임시 List<Integer>는 로 타입인 List가 되고, List<Integer>[]는 List[]가 되기에 정상적으로 objects에 들어가며 ArrayStoreException이 발생하지 않습니다.
- (5)에서 stringLists라는 제네릭 배열의 0번째 인덱스에 있는 List를 꺼내서 get 으로 0번째 값을 꺼내려고 할 때 문제가 발생합니다.
- 컴파일러는 꺼낸 원소를 자동으로 String으로 형변환하는데 실제 원소는 Integer 타입이기 때문에 런타임시 ClassCastException이 발생합니다. 즉 제네릭의 장점인 타입 안전성이 무용지물 됩니다.
💡 실체화 불가 타입(non-reifiable type)
- 실체화 불가 타입이란 실체화가 되지 않아서 런타임에는 컴파일타임보다 타입 정보를 적게 가지는 타입입니다. 소거 매커니즘으로 인해 매개변수화 타입 중에서 실체화가 가능한 타입은 List<?>, 또는 Map<?, ?>와 같은 비한정적 와일드카드 타입뿐입니다.
- 아래는 실체화 불가 타입입니다.
- 정규 타입 매개변수 E
- 제네릭 타입 List<E>
- 매개변수화 타입 List<String>
💡 제네릭 타입과 가변인수 메서드(varargs method)
- 제네릭과 가변인수 메서드를 같이 사용하면 경고 메세지가 출력되는데, 가변인수 메서드를 호출할 때마다 가변인수 매개변수를 담을 배열이 만들어지는데 이때 이 배열의 원소가 실체화 불가 타입이라면 경고가 발생하게 됩니다. 그리고 이러한 경고는 @SafeVarags라는 어노테이션으로 대체할 수 있습니다.
💡 예제 코드
- 배열로 형변환시 제네릭 배열 생성 오류로, 비검사 형변환 경고같은 대부분의 문제들은 배열(E[]) 대신 컬렉션(List<E>)로 대체할 수 있습니다. 컬렌션을 사용함으로써 코드가 복잡해지고 성능이 떨어질 수 있지만 대신 타입 안정성과 상호 운용성측에서 이점을 가질 수 있습니다.
- 제네릭을 사용하지 않는 Object 객체 배열을 사용한 코드입니다. 이 코드는 choose 메서드에서 호출할 때마다 반환되는 타입은 Object 타입이기 때문에 원하는 타입으로 형변환을 해줘야 합니다. 그리고 내가 변환하고자 하는 타입과 다를 경우 형변환 오류가 발생합니다.
public class Chooser {
private final Object[] choiceArray;
public Chooser(Collection choiceArray) {
this.choiceArray = choiceArray.toArray();
}
public Object choose() {
Random random = ThreadLocalRandom.current();
return choiceArray[random.nextInt(choiceArray.length)];
}
}
- 아래 처럼하면 어떠한 경고도 없이 컴파일이 됩니다. 물론 코드의 양이 늘고 조금은 복잡해지고 속도도 조금 떨어질 수 있지만 타입안정성을 확보할 수 있습니다.
public class ChooserV3<T> {
private final List<T> choiceList;
public ChooserV3(Collection<T> choiceList) {
this.choiceList = new ArrayList<>(choiceList);
}
public Object choose() {
Random random = ThreadLocalRandom.current();
return choiceList.get(random.nextInt(choiceList.size()));
}
}
💡 정리
- 배열은 공변이고 실체화되기 때문에 런타임시에는 타입 안전하지만 컴파일시에는 불안전합니다.
- 제네릭은 불공변이고 타입 정보가 런타임시에 소거되므로 런타임시에는 불안전하지만 컴파일시에는 안전합니다.
- 둘을 섞어쓰면 서로의 장점이 사라지기 때문에 따로 사용하는게 좋습니다.
- 컴파일 오류나 경고를 만나면 배열을 리스트로 대체하는 방법을 고려해보고 적용해보는게 좋습니다.
이번 아이템은 어려운 부분이 많아서 따로 학습이 필요할 것 같습니다..!
참고자료)
https://catsbi.oopy.io/83635cfe-1cab-43f2-a943-56a9efd83fb2
728x90
반응형
'스터디 > 이펙티브 자바' 카테고리의 다른 글
이펙티브 자바 - Item30. 이왕이면 제네릭 메서드로 만들라. (0) | 2022.07.21 |
---|---|
이펙티브 자바 - Item29. 이왕이면 제네릭 타입으로 만들라. (0) | 2022.07.20 |
이펙티브 자바 - Item27. 비검사 경고를 제거하라. (0) | 2022.07.18 |
이펙티브 자바 - Item26. 로 타입은 사용하지 말라. (0) | 2022.07.17 |
이펙티브 자바 - Item24. 멤버 클래스는 되도록 static으로 만들라. (0) | 2022.07.16 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
TAG
- redis sorted set
- redis 대기열 구현
- @ControllerAdvice
- microkernel architecture
- 자바 백엔드 개발자 추천 도서
- 람다 표현식
- spring boot excel download paging
- java userThread와 DaemonThread
- 공간 기반 아키텍처
- 레이어드 아키텍처란
- pipeline architecture
- 서비스 기반 아키텍처
- polling publisher spring boot
- spring boot 엑셀 다운로드
- spring boot poi excel download
- 트랜잭셔널 아웃박스 패턴 스프링 부트 예제
- spring boot redis 대기열 구현
- spring boot redisson destributed lock
- spring boot redisson sorted set
- JDK Dynamic Proxy와 CGLIB의 차이
- service based architecture
- spring boot excel download oom
- 트랜잭셔널 아웃박스 패턴 스프링부트
- redis sorted set으로 대기열 구현
- space based architecture
- pipe and filter architecture
- transactional outbox pattern spring boot
- java ThreadLocal
- spring boot redisson 분산락 구현
- transactional outbox pattern
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
글 보관함