ITEM 26 "로 타입은 사용하지 말라"
같은 기능을 하는 코드이지만 자료형에 따라 오버로딩 함수를 여러 개 만들어야 한다고 생각해보자.
자연스레 타입에 상관없이 제네릭하게 코드를 구성하고 싶을 것이다. 자바에서도 타입을 매개변수로 하여 클래스와 인터페이스를 작성할 수 있으며 제네릭 클래스 혹은 제네릭 인터페이스라고 한다. 두 정의를 통틀어 제네릭 타입이라 한다.
꺽쇠괄호 안에 실제 타입 매개변수들을 나열해서 매개변수화 타입을 정의한다. 예를 들어 List<String> 과 같은 식이다.
이 제네릭 타입을 정의하면 로 타입도 함께 정의되는데 이 로 타입을 주의해야 한다. List<String> 의 로 타입은 List 이다. 선언 시에 타입에 대한 정보가 지워진 것처럼 동작하는데 하위 호환성을 위해 자바 9 까지 계속 허용하고 있다.
public class EffectiveJavaTest {
static class Stamp {
public Stamp () {}
public void cancel() {}
}
static class Coin {
public Coin () {}
}
private final static Collection stamps = new ArrayList<>();
public static void main(String[] args) {
stamps.add(new Coin());
for (Iterator i = stamps.iterator(); i.hasNext();) {
Stamp stamp = (Stamp) i.next();
stamp.cancel();
}
}
}
선언 시에 타입에 대한 정보가 없으니 아무런 원소를 넣어도 컴파일 오류가 발생하지 않는다. 위 코드처럼 반복자로 원소를 비교하거나 꺼낼 때 런타임 오류가 발생한다. 원소 추가는 Coin 클래스로, 원소 활용은 Stamp 클래스로 할 수 있는 것을 볼 수 있다. 항상 느끼지만 자유도가 높으면 프로그래머의 실수를 유발하는 것 같다.
Collection<Stamp> 로 타입을 지정해준다면, 컴파일 타임 때 오류를 발견할 수 있다. 런타임 오류의 경우, 문제 발생 코드와 원인을 제공하는 코드가 물리적으로 상당히 떨어지면 어디서 오류가 발생했는지 모든 소스코드를 다 뒤져야 할 수도 있다. 그런데 컴파일 타임 때 오류를 발생하면 타입 안전하게 코드를 작성할 수 있다.
정말 로 타입을 쓰고 싶다면, List<Object> 처럼 임의의 객체를 허용하는 매개변수화 타입을 권장하고 있다.
매개변수로 List 를 받는 메서드에 List<String> 을 넘길 수 있지만, List<Object> 를 받는 메서드에는 넘길 수 없다.
더 나아가, 원소의 타입을 몰라도 되는 로 타입을 쓰고 싶을 때 <?> 비한정적 와일드카드를 쓸 것을 권장하고 있다.
<?> 는 < ? extends Object > 의 줄임 표현으로 어떤 자료형의 객체도 매개변수로 받겠다는 의미이다. 로 타입하고 같아보이지만, 처음에 넣은 타입 외에는 다른 원소를 넣을 수 없다는 특징이 있다.
public class EffectiveJavaTest {
private static int test(Set<?> s1, Set<?> s2) {
s1.add("test");
}
public static void main(String[] args) {
Set<Integer> s1 = new HashSet<>();
s1.add(Integer.valueOf(10));
Set<Integer> s2 = new HashSet<>();
s2.add(Integer.valueOf(20));
test(s1, s2);
}
}
< ? extends Object > 라고 많은 블로그에서 정의하고 있지만 사실 상 <? extends 입력된 첫번째 자료형> 이지 않을까 싶다. 그대로 컴파일하면 아래와 같은 에러를 내뿜는다. Integer 용 Set 에 String 을 add 하기 때문에 타입 컨벤션 오류가 발생한 것이다.
error: incompatible types: String cannot be converted to CAP#1 s1.add("test"); ^ where CAP#1 is a fresh type-variable: CAP#1 extends Object from capture of ? |
컴파일 단계 때, 타입 검사를 할 수 있어 로 타입보다 더 좋아보이지만 extends 와 super 키워드 없이 (상한제한자, 하한제한자) 단독으로 사용하지 않을 것을 권장하고 있다. 자세한 내용은 아래 링크를 참조하는 것을 추천한다.
사용법 - Java에서 클래스 <?>은 무엇을 의미합니까? - 스택 오버플로 (stackoverflow.com)
그렇다고 로 타입을 아예 안 쓰지는 않는다. 로 타입을 써야되는 경우는 두 가지이다.
class 리터럴에는 로 타입을 써야 한다. 어떤 클래스를 표현하고자 할 때 xxx.class 로 표기하는데 여기에 제네릭 타입을 쓰면 안 된다. 두 번째는 instanceof 연산자이다. instanceof 연산자의 비연산자도 굳이 제네릭 타입을 쓸 필요가 없다.
"로 타입은 지양하자."
'독후감 > Effective JAVA' 카테고리의 다른 글
[Effective JAVA] 28 "배열보다는 리스트를 사용하라" (1) | 2022.05.25 |
---|---|
[Effective JAVA] 27 "비검사 경로를 제거하라" (0) | 2022.05.24 |
[Effective JAVA] 25 "탑 레벨 클래스는 한 파일에 하나만 담으라" (0) | 2022.05.21 |
[Effective JAVA] 24 "멤버 클래스는 되도록 static 으로 만들어라" (0) | 2022.05.20 |
[Effective JAVA] 23 "태그 달린 클래스보다는 클래스 계층구조를 활용하라" (0) | 2022.05.18 |