본문 바로가기

개발이야기/Effective Java

[Effective Java]새 코드에는 무인자 제네릭 자료형을 사용하지 마라


자바 제네릭을 제대로 이해하기 위해서는 아래와 같은 용어에 대한 명확한 이해가 필요하다.


 용어

예 

 Parameterized type

 List<String>

 Actual type parameter

 String

 Generic parameter

 List<E>

 Formal type parameter

 E

 Unbounded wildcard type

 List<?>

 Raw type

 List

 Bounded type parameter <E extends Number>

 Recursive type bound

 <T extends Comparable<T>>

 Bounded wildcard type

 List<? extends Number>

 Generic method

 static <E> List<E> asList(E() a)

 Type token

 String.class



자바 1.5이전에는 아래와 같이 사용하곤 했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
    public static void main(String[] args){
        
        Collection studentNames = new ArrayList<>();
        studentNames.add("wonyoung");
        studentNames.add("somin");
        studentNames.add(1);
 
        for (Iterator iterator = studentNames.iterator(); iterator.hasNext();) {
            String name = (String) iterator.next();
            System.out.println(name);
        }
 
    }
}
cs


위와 같이 쓰면 아래와 같이 ClassCastException예외가 발생하게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
wonyoung
somin
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at com.jp.Main.main(Main.java:10)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
 
Process finished with exit code 1
 
cs


항상 강조하지만 오류는 가장 빨리 발견해야 하며, 컴파일 할때 발견할 수 있으면 가장 이상적이다. 그러나 위 예제는 컴파일때는 전혀 이상한 점을 못찾다가 실행 중에 발견되게 된다.


컴파일러는 위 Collection 자료형이 "String객체만 보관한다" 라는 명시적인 문구가 없기때문에 문제가 발생하게 되는 것이다. 이것을 해결할 방법이 바로 아래와 같이 제네릭을 쓰는 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
    public static void main(String[] args){
 
        Collection<String> studentNames = new ArrayList<>();
        studentNames.add("wonyoung");
        studentNames.add("somin");
        studentNames.add(1); // compile error 발생 || Error:(15, 26) java: incompatible types: int cannot be converted to java.lang.String
 
        for (Iterator iterator = studentNames.iterator(); iterator.hasNext();) {
            String name = iterator.next();
            System.out.println(name);
        }
 
    }
}
cs


또한 위 같이 제너릭을 쓰게 되면 형변환을 하지 않아도 된다.


물론 개발자가 조심해서 쓴다는 가정 하에 제너릭을 쓰지 않을 수도 있다. 하지만 그래서는 안된다. 무인자 자료형을 쓰면 형 안전성이 사라지고, 제네릭의 장점 중 하나인 표현력(expressiveness) 측면에서 손해를 보게된다.

(그런데 자바는 형 안전성을 반드시 유지해야하나? 형 안전성이 유지가 안되는 Javascript는 다른 언어라고 이해하면 되나?)


무인자 자료형이 아직도 자바에서 지원되는 이유는 자바에서 제네릭을 도입하면서 다음단계로 넘어갈때 이미 너무나도 많은 양의 코드가 제네릭 없이 구현된 상태였기 때문이다.


또한 제네릭에서 아래와 같이 한정적 자료형으로 사용도 가능하다. 이건 Super class를 상속받은 class만 들어 올 수 있고 Casting을 Super class으로 해준다

1
2
3
4
5
6
public static void printAll(ArrayList<extends Superclass> list)
{
    for(Childclass u : list){
        System.out.println(u);
    }
}
cs


End of Document