티스토리 뷰
* 이펙티브 자바 2/E를 읽고 공부하기 위해 기록한 게시글입니다.
23. 새 코드에는 무인자 제네릭 자료형(raw type)을 사용하지 마라
자바에는 제네릭(Generic)이라는 개념이 있다.
먼 옛날 제네릭이 도입되기 이전에는 컬렉션에서 객체를 읽을때 마다 형변환(type cast)를 해줘야 했다. 그러다 누군가가 컬렉션에 엉뚱한 자료형 객체라도 넣어놓기라도 했으면 캐스팅 오류가 나고 만다.
하지만 제네릭을 사용하면 컬렉션에 넣을 객체의 자료형이 무엇인지를 컴파일러에게 알려줄 수 있다.
-> 형변환 코드는 컴파일러가 알아서 만들어주고, 컬렉션에 잘못된 자료형의 객체를 넣으려는 시도는 컴파일 과정에서 미리 차단해준다. 제네릭 덕분에 안전하고 명료한 개발이 가능해진다!
선언부에 형인자(type parameter)가 포함된 클래스나 인터페이스를 제네릭 클래스나 제네릭 인터페이스라고 부르고, 이 둘을 합쳐 제네릭 자료형(generic type)이라 한다. (예시: List<E>, Collection<E> ...)
각 제네릭 자료형은 형인자 자료형(parameterized type) 집합을 정의한다. 이 집합은 이름 뒤에 < > 기호로 감싼 실 형인자(actual type parameter)목록이 붙은 클래스나 인터페이스들로 구성되는데, 이 실인자들은 제네릭 자료형의 형식 형인자(formal type parameter) 각각에 대응된다. 말이 되게 어렵다.
List<String>은 원소 자료형이 String인 리스트를 나타내는 형인자 자료형이다.
그리고 각 제네릭 자료형은 새로운 무인자 자료형(raw type)을 정의하는데, 무인자 자료형은 실 형인자가 없는채로 사용되는 제네릭 자료형이다. 그냥 < > 없이 List 쌩으로 사용하는 스타일.
- 무인자 자료형은 자료형 선언에서 제네릭 자료형 정보가 전부 삭제된 것 처럼 동작한다. 제네릭 도입 이전과 동일해진다.
-> 무인자 자료형 컬렉션은 엉뚱한 자료형의 객체를 넣어도 컴파일 시에 아무런 문제가 발생하지 않고, 나중에 객체를 꺼내올 때 캐스팅 오류를 발생시키게 된다. 컴파일러의 도움은 하나도 받을 수 없게된다.
- 오류는 가능한 한 빨리 발견해야 하며, 컴파일 단계에서 발견하는 것이 가장 이상적이다.
하지만 제네릭을 사용하게 되면 컴파일러에게 컬렉션에 담길 객체의 자료형이 무엇인지를 미리 알려줄 수 있다.
private final List<Stamp> stampList = ...
-> Stamp 리스트에 Stamp 자료형이 아닌 객체를 넣으려고 하면 컴파일 단계에서 그냥~ 콱! 못들어오게 막아버린다.
또한, 형인자 자료형을 사용하면 컬렉션에서 원소를 꺼낼 때 형변환을 따로 하지 않아도 컴파일러가 자동으로 형변환 코드를 삽입해준다.
무인자 자료형을 쓰면 형 안전성이 사라지고, 제네릭의 장점 중 하나인 표현력 측면에서 손해를 보게된다.
쓸 이유가 없는데도 아직 자바에서 무인자 자료형을 지원하는 이유는,, 이전 호환성(mirgation compatibility)로 인해 구시대 유물 코드와도 호환이 되어야 하기 때문.. 그렇기에 새로 만드는 코드에서는 무인자 자료형을 쓸 이유가 없다. (예외 있음)
* List와 List<Object>는 서로 다르다!!
두 리스트 모두 아무런 객체나 넣을 수 있다는 것은 동일해 보인다. 뭐가 다른걸까?
List는 형 검사 절차를 아예 완전히 생략한 것이고, List<Object>는 아무 객체나 다 넣을 수 있다는 것을 컴파일러에게 알리는 것이다.
List를 인자로 받는 메서드에는 List<String>을 전달할 수 있지만, List<Object>를 인자로 받는 메서드에는 List<String>을 전달할 수 없다. List<String>은 List의 하위 자료형이지만 List<Object>의 하위 자료형은 아니기 때문이라고 한다.
(String은 Object의 하위 타입이지만 컬렉션에서는 다르다)
= List와 같은 무인자 자료형을 사용하면 형 안전성을 잃지만 List<Object>와 같은 형인자 자료형을 사용하면 그렇지 않다.
public class EffectiveJava {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
unsafeAdd(stringList, Integer.valueOf(42));
String s = stringList.get(0); // ClassCastException 발생
}
private static void unsafeAdd(List list, Object o) {
list.add(o);
}
}
<무인자 자료형을 사용한 경우> - 컴파일 시점에서 오류를 잡지 못하고, 런타임 중에 형변환을 하다가 예외가 터져버린다.
public class EffectiveJava {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
unsafeAdd(stringList, Integer.valueOf(42));
String s = stringList.get(0);
}
private static void unsafeAdd(List<Object> list, Object o) {
list.add(o);
}
}
<제네릭을 사용한 경우> - 아싸리 컴파일 시점부터 incompatible type 에러를 띄워준다.
컬렉션에 들어갈 원소들의 자료형을 모르고 상관할 필요가 없다면 어떻게 하면 좋을까?
그럴때는 비한정적 와일드카드 자료형(unbounded wildcard type)를 사용하는 것이 안전한 방법이다.
- 제네릭 자료형을 쓰긴 쓰고싶은데 실제 형인자가 무엇인지 모르거나, 신경쓰고 싶지 않을때는, 형인자를 "?"로 쓰면 된다. (List<?>, Set<?>, Collections<?>...)
무인자 자료형을 사용한 Set과 와일드카드를 사용한 Set<?>는 또 대체 뭐가 다를까?
당연하겠지만 와일드카드 자료형은 안전하지만 무인자 자료형은 그렇지 않다.
무인자 자료형 컬렉션에는 아무 객체나 넣을 수 있어 컬렉션의 자료형 불변식이 쉽게 깨지지만, 와일드카드 자료형에는 null 이외의 어떠한 원소도 넣을 수 없다....!! (어떤 자료형의 객체를 담는 컬렉션인지 알 수 없기 때문이다)
-> 뭔가를 넣으려고 한다면 컴파일 과정에서 차단시킨다.
-> 컬렉션의 자료형 불변식이 위반되지 않도록 컴파일러가 도와주기는 한다.. 아무것도 못넣게 해서 불변을 지키는 것;;;
null 이외의 어떠한 원소도 넣을 수 없을 뿐만 아니라 어떤 자료형의 객체를 꺼낼 수 있는지도 알 수 없다. 이거 완전 군대식 일처리랑 똑같다;;
이러한 제약이 불만이라면 제네릭 메서드를 사용하거나, 한정적 와일드카드 자료형을 사용하면 된다.
* 한정적 와일드카드 자료형 = List<? extends Number>와 같은 자료형.. 많이 눈에 익는다
하지만.. 무인자 자료형을 사용해야 하는 몇가지의 예외가 있다. 이런게 안나오면 섭하다.
1. 클래스 리터럴(class literal)에는 반드시 무인자 자료형을 사용해야 한다.
- 자바 표준에 따르면 클래스 리터럴에는 형인자 자료형을 사용할 수 없다. (배열 자료형, 기본 자료형은 가능)
-> List.class, String[].class, int.class (O)
-> List<String>.class, Set<?>.class (X)
* 클래스 리터럴이란 클래스이름.class 형식 표현법을 뜻함.
2. instanceof 연산자는 비한정적 와일드카드 자료형 이외의 형인자 자료형에는 적용할 수 없다.
- 제네릭 자료형 정보는 프로그램이 실행될 때는 지워지기 때문에 instanceof 연산자는 비한정적 와일드카드 자료형 외의 형인자 자료형에 적용할 수 없다.
- instnaceof에 비한정적 와일드카드 자료형을 써봤자 코드만 지저분해질 뿐 달라지는게 없다. 그렇기에 instnaceof 연산자를 적용할 때는 무인자 자료형을 사용해야 한다.
if(o instanceof Set) {
...
}
(결론)
무인자 자료형(raw type)을 사용하면 런타임 중에 예외가 발생할 수 있으므로 무인자 자료형은 사용하지 말아야 한다.
'JAVA' 카테고리의 다른 글
배열보다 리스트를 사용해라 (0) | 2022.07.26 |
---|---|
unchecked warning은 제거하라 (0) | 2022.07.26 |
자바의 내부 클래스와 중첩 클래스에 대해.. (0) | 2022.07.25 |
인터페이스를 활용해 전략 패턴을 사용하자 (0) | 2022.07.25 |
추상 클래스 보다는 인터페이스 (0) | 2022.07.24 |