제네릭 - 규칙 29 형 안전 다형성 컨테이너를 쓰면 어떨지 따져보라

JAVA/Effective Java|2018. 5. 29. 23:02


제네릭은 Set이나 Map과 같이 하나 자료형을 가진 원소들을 담는 컨테이너에 가장 많이 사용된다.
ex) Map<String, String>, Set<Integer>

그렇기 때문에 형인자는 컨테이너별로 고정되게 되어있다.

그러나 가끔 여러개의 자료형을 Map과 콜렉션에 컨테이너로서 사용하고 싶을 경우가 있을 것이다.

이는 다음과 같은 접근법을 사용하면 가능하다.



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
import java.util.HashMap;
import java.util.Map;
 
public class Rule29   {
    public Map<Class<?>, Object> map = new HashMap<>();
 
    public <T> void putData(Class<T> type, T instance) {
        map.put(type, instance);
    }
    
    public Object getData(Class<?> type) {
        return type.cast(map.get(type));
    }
    
}
 
 
public class Main {
    public static void main(String agrs[]) {
        Rule29 rule = new Rule29();
        rule.putData(String.class"test");
        rule.putData(Integer.class222);
        rule.putData(Student.classnew Student("cjung"));
        
        System.out.println(rule.getData(String.class));
        System.out.println(rule.getData(Integer.class));
        System.out.println(((Student) rule.getData(Student.class)).getStudent());
        
    }
}
cs




위와 같은 방법을 사용하면 키를 자료형을 사용하고
값을 그 자료형에 맞는 데이터를 넣을 수 있다.

이렇게 모든 키의 자료형이 서로 다른 클래스를 
형 안전 다형성 컨테이너(typesafe heterogeneous container)라고 한다.

그리고

위와 같이 Rule29 객체에서 setData, getData에서 사용된 Class 객체는 제네릭 클래스로서
컴파일 시간 자료형이나 실행시간 자료형 정보를 메소드들에 전달할 목적으로 class 리터럴을 이용하는 경우를 자료형 토큰(type token)이라 한다.


이런 형 안전 다형성 컨테이너를 사용하면 다음과 같은 장단점이 있다.

[형 안전 다형성 컨테이너의 장점]
1. 이런 방식을 사용할 경우 내가 요청한 자료형만을 반환하게 되므로  형 안정성이 보장된다.
ex) String.class를 키를 사용하는 데이터는 무조건 String 데이터만 반환한다.

2. Rule29에서 사용한 public Map<Class<?>, Object> map = new HashMap<>()을 살펴보자.
자료형으로 와일드카드 자료형을 사용하였기 때문에 상이한 형인자 자료형을 가질 수 있다.
ex) Class<String>, Class<Integer>

3. getData메소드에서 Class<?>의 cast 메소드를 사용함으로써 동적 형변환을 사용한다.
=> 키로사용된 Class의 자료형과 일치하면 Casting되서 정확한 데이터가 반환될 것이고 그렇지 않은 경우 ClassCastException이 발생할 것이다.
=> 이를 이용하여 무점검 형변환하는 코드가 없는 형 안전성을 확보 할 수 있다.


[형 안전 다형성 컨테이너의 단점]
1. 악의적으로 Rule29 객체의 형안전성을 어길 수 있다.
=> Class 객체를 무인자 형태로 사용할 경우 형 안전성이 훼손된다.

2. 악의적으로 rule.putData(String.class, 123)과 같이 자료형과 다른 데이터를 삽입할 수 있다.
-> put 하는 순간에 type을 확인하여 맞을 때만 삽입하도록 한다.
-> map.put(type, type.cast(instance));
-> java.util.Collections에는 정적 팩토리 매서드들이 존재한다. (checkedSet, checkedList, checkedMap)
     => 이런 종류의 정적 팩토리 메소드들은 형안정성을 보장한다.
     => 이것을 사용해서 형 안정성을 확보할 수 있다.


1
2
3
4
5
6
7
8
9
10
    public static void main(String... args) {
        List<String> list = new ArrayList<>();
        list = Collections.checkedList(list, String.class);
        list.add("one");
        System.out.println(list);
 
        List list2 = list;
        list2.add(2); // ClassCastException을 발생시킨다. (기존의 List의 경우 raw type으로 사용하였을 경우 용인된다.)
        System.out.println(list2);
    }
cs



3. 실체화 불가능 자료형에는 쓰일 수 없다.
ex) List<String>, List<Integer>은 다음과 같은 Class 객체를 얻을 수 없다. (List<Integer>.class)
=> List.class를 사용하여 해결할 수는 있다.

[기타]
특정 자료형 토큰을 사용하도록 제한하고 싶은경우 한정적 형인자나 한정적 와일드카드를 사용할 수 있다.

ex) public <T extends Annotation> T getAnnotation(Class<T> annotationType);


출처 : 조슈아 블로크, 『 Effective Java 2/E』, 이병준 옮김, 인사이트(2014.9.1), 규칙29 인용.

댓글()