-
Notifications
You must be signed in to change notification settings - Fork 3
Open
Labels
5장 제네릭이펙티브 자바 5장 (제네릭)이펙티브 자바 5장 (제네릭)
Description
Discussed in https://github.com/orgs/Study-2-Effective-Java/discussions/59
Originally posted by bunsung92 January 12, 2023
📝 구성 - 구성애 선생님 아님.
Table of contents generated with markdown-toc
0. 들어가기에 앞서 🧐
배열(Array)
과리스트(List)
의 메모리 할당 방법적인 차이의 접근 주제가 아니다.- 해당 주제는
배열(Array)
과리스트(List)
공변성, 불공변성과 실체화와 타입 정보 소거에 포커스를 두고 있다. - 공변성과 불공변성
1. 배열(Array) ✨
- 해당 그림은
배열(Array)
에 다른 타입의 데이터 삽입을 위한 그림이다. - 컴파일 시점에
배열(Array)
은 다른 타입의 데이터가 삽입 되어도 눈치채지 못한다.lint는 눈치챘다.
1.1 배열의 공변성
- Sub가 Super의 하위 타입이라면 배열 Sub[]는 배열 Super[]의 하위타입이 된다.
Object[] objects = new Long[1]; // Long = Sub, Object = Super 선언 가능!
1.2 배열의 실체화
- 배열은
런타임
에도 자신이 담기로 한 원소의 타입을 인지하고 확인한다.
Object[] objects = new Long[1];
objects[0] = "타입이 달라 넣을 수 없다.";
- 코드를 실행 시키면
- 런타임을 상속한
ArrayStoreException
예외를 발생시킨다. - 즉 해당 코드는 런타임에 가서야 오류가 있음을 알게되는 것이다!!
- 운영중인 서비스에 해당 코드가 배포 되었다면?? 하필 테스트 코드를 삽입하지 않아 Jenkins 및 GitHub Action이 발견하지 못했다면??
- 😢 👊 💥
2. 리스트(List) ✨
- 해당 그림은 컴파일 시점에 컴파일러가 경고를 한다.
2.1 리스트의 불공변성
- 서로 다른 타입 Type1과 Type2가 있을 때,
List<Type1>
은List<Type2>
의 하위 타입도 아니고 상위 타입도 아니다.
2.2 리스트의 타입 정보 소거
제네릭의 타입 정보 소거 간단 예제 보기
public static <E> boolean containsElement(E[] elements, E element) { // 컴파일 시점
for (E e : elements) {
if (e.equals(element)) {
return true;
}
}
return false;
}
public static boolean containsElement(Object[] elements, Object element) { // 런타임 시점
for (Object e : elements) {
if (e.equals(element)) {
return true;
}
}
return false;
}
- 제네릭은 타입 정보가
런타임
에는 소거(ensure) 된다. - 타입 정보 소거의 기능을 제네릭이 지원되기 전의 레거시 코드와 제네릭 타입을 함께 사용할 수 있게 해주는 매커니즘이다.(자바 5가 순조롭게 전환 될 수 있었던 이유)
- 타입 정보 소거 때문에 배열과 제네릭은 잘 어우러지지 못한다.
- e.g)
new List<E>[] // 제네릭 타입
,new List<String>[] // 매개변수화 타입
,new E[] // 타입 매개변수
- 위의 모든 예는 컴파일 시 제네릭 배열 생성 오류를 일으킨다.
- e.g)
2.3 제네릭 배열을 만들지 못하는 이유는? 🤔
- 타입이 안전하지 못하다.
- 컴파일러가 자동 생성한 형변환 코드에서 런타임시
ClassCastException
이 발생할 수 있다.
제네릭이 ClassCastException을 막기 위해 사용 되었단 것을 알아야 한다.
- 배열의 가변성을 높이고 싶다면? 아이템 31. 한정적 와일드카드를 사용해 API 유연성을 높이라 #58 을 참조 해보자.
3. 배열을 리스트로 사용 해 보자! ✨
생성자에서 컬렉션을 받는 Chooser 클래스를 예로 살펴보자.
version 1. - 제네릭 적용 전
public class Chooser {
private final Object[] choiceArray;
public Chooser(Object[] choiceArray) {
this.choiceArray = choiceArray;
}
public Object choose() {
Random random = ThreadLocalRandom.current();
return choiceArray[random.nextInt(choiceArray.length)];
}
}
- 아이템 29. 이왕이면 제네릭 타입으로 만들라 #62 를 새기며 해당 코드를 수정해 보자
version 2. - 제네릭 첫 적용
public class Chooser<T> {
private final T[] choiceArray;
public Chooser(Collection<T> choiceArray) {
this.choiceArray = (T[]) choiceArray.toArray();
}
public Object choose() {
Random random = ThreadLocalRandom.current();
return choiceArray[random.nextInt(choiceArray.length)];
}
}
- 해당 코드는 T의 타입을 알 수 없다는 컴파일 경고를 발생한다.
- 형 변환이 런타임에 안전한지 보장 할 수 없다는 말이다.
version 3. - 타입 안정성 확보!
public class Chooser<T> {
private final List<T> choiceArray;
public Chooser(Collection<T> choiceArray) {
this.choiceArray = new ArrayList<>(choiceArray);
}
public Object choose() {
Random random = ThreadLocalRandom.current();
return choiceArray.get(random.nextInt(choiceArray.size()));
}
}
4. 핵심 정리 📚
- 배열은 공변이고 실체화 된다.
- 제네릭은 불공변이고 타입 정보가 소거된다.
- 배열은 런타임에 타입이 안전하지만, 컴파일에는 그렇지 않다.
- 제네릭은 컴파일에 타입이 안전하지만, 런타임에는 그렇지 않다.
- 둘을 섞어 사용하며 오류 및 경고에 대한 스트레스 보다는 배열을 리스트로 만드는데 초점을 맞춰 보도록 하자.
5. 회고 🧹
2023.01.15 (일)
- 제네릭과 배열의 비교라는 점에서 조금은 신선했다.
- 공변과 불공변의 용어가 다루기 쉽지 않았다고 생각한다.
- 언젠가 제네릭을 다뤄 클래스를 만들게 된다면 제네릭의 본질과 맞물려 있는 자바의 변성에 대해 다시 한번 정리 해 볼 수 있도록 하자.
- 자바 변성
참조
https://velog.io/@lsj8367/%EC%9E%90%EB%B0%94-%EB%B3%80%EC%84%B1Variance
Metadata
Metadata
Assignees
Labels
5장 제네릭이펙티브 자바 5장 (제네릭)이펙티브 자바 5장 (제네릭)