아이템 33. 타입안전 이종컨테이너를 고려하라.

이펙티브 자바

아이템 33. 타입안전 이종컨테이너를 고려하라.

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
31
32
33
34
35
36
37
38
39
40
41
42

package com.github.sejoung.codetest.generics;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

// 타입 안전 이종 컨테이너 패턴 (199-202쪽)
public class Favorites {
// 코드 33-3 타입 안전 이종 컨테이너 패턴 - 구현 (200쪽)
private Map<Class<?>, Object> favorites = new HashMap<>();

public <T> void putFavorite(Class<T> type, T instance) {
favorites.put(Objects.requireNonNull(type), instance);
}

public <T> T getFavorite(Class<T> type) {
return type.cast(favorites.get(type));
}

// 코드 33-2 타입 안전 이종 컨테이너 패턴 - 클라이언트 (199쪽)
public static void main(String[] args) {
Favorites f = new Favorites();

f.putFavorite(String.class, "Java");
f.putFavorite(Integer.class, 0xcafebabe);
f.putFavorite(Class.class, Favorites.class);

String favoriteString = f.getFavorite(String.class);
int favoriteInteger = f.getFavorite(Integer.class);

System.out.println(favoriteString);
System.out.println(favoriteInteger);

Class<?> favoriteClass = f.getFavorite(Class.class);

System.out.printf("%s %x %s%n", favoriteString,
favoriteInteger, favoriteClass.getName());
}
}


실행결과

1
2
3
4
5
6
7

Java
-889275714
Java cafebabe com.github.sejoung.codetest.generics.Favorites

Process finished with exit code 0

위에 코드 처럼 간단하게 타입안전 이종 컨테이너를 구현할수있는데 여기서 2가지 문제 점이 있다.

  • 악의적인 클라이언트가 Class객체를 제네릭이 아닌 로타입으로 넘기면 타입안정성이 깨진다.
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

package com.github.sejoung.codetest.generics;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

// 타입 안전 이종 컨테이너 패턴 (199-202쪽)
public class Favorites {
// 코드 33-3 타입 안전 이종 컨테이너 패턴 - 구현 (200쪽)
private Map<Class<?>, Object> favorites = new HashMap<>();

public <T> void putFavorite(Class<T> type, T instance) {
favorites.put(Objects.requireNonNull(type), instance);
}

public <T> T getFavorite(Class<T> type) {
return type.cast(favorites.get(type));
}
/*

// 코드 33-4 동적 형변환으로 런타임 타입 안전성 확보 (202쪽)
public <T> void putFavorite(Class<T> type, T instance) {
favorites.put(Objects.requireNonNull(type), type.cast(instance));
}
*/

// 코드 33-2 타입 안전 이종 컨테이너 패턴 - 클라이언트 (199쪽)
public static void main(String[] args) {
Favorites f = new Favorites();

f.putFavorite(String.class, "Java");
f.putFavorite(Integer.class, 0xcafebabe);
f.putFavorite(Class.class, Favorites.class);
f.putFavorite((Class)Integer.class, "나는 바보");
String favoriteString = f.getFavorite(String.class);
int favoriteInteger = f.getFavorite(Integer.class);

System.out.println(favoriteString);
System.out.println(favoriteInteger);

Class<?> favoriteClass = f.getFavorite(Class.class);

System.out.printf("%s %x %s%n", favoriteString,
favoriteInteger, favoriteClass.getName());
}
}


실행결과

1
2
3
4
5
6
7
8
Exception in thread "main" java.lang.ClassCastException: Cannot cast java.lang.String to java.lang.Integer
at java.base/java.lang.Class.cast(Class.java:3606)
at com.github.sejoung.codetest.generics.Favorites.getFavorite(Favorites.java:17)
at com.github.sejoung.codetest.generics.Favorites.main(Favorites.java:36)

Process finished with exit code 1


위에 코드에서 f.putFavorite((Class)Integer.class, “나는 바보”); 처럼 로타입을 넘기면
컴파일은 정상인데 실행시에 ClassCastException이 발생한다.

하지만 일반 컬랙션에도 다음같은 문제가 있기 때문에 어느정도 감수한다면 타입안정성을 얻을수 있다.

Favorites 코드가 불변식을 어기는 일이 없도록 보장하려면 putFavorite를 수정하면된다.

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
31
32
33
34
35
36
37
38
39
40
41
42

package com.github.sejoung.codetest.generics;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

// 타입 안전 이종 컨테이너 패턴 (199-202쪽)
public class Favorites {
// 코드 33-3 타입 안전 이종 컨테이너 패턴 - 구현 (200쪽)
private Map<Class<?>, Object> favorites = new HashMap<>();

public <T> T getFavorite(Class<T> type) {
return type.cast(favorites.get(type));
}

// 코드 33-4 동적 형변환으로 런타임 타입 안전성 확보 (202쪽)
public <T> void putFavorite(Class<T> type, T instance) {
favorites.put(Objects.requireNonNull(type), type.cast(instance));
}

// 코드 33-2 타입 안전 이종 컨테이너 패턴 - 클라이언트 (199쪽)
public static void main(String[] args) {
Favorites f = new Favorites();

f.putFavorite(String.class, "Java");
f.putFavorite(Integer.class, 0xcafebabe);
f.putFavorite(Class.class, Favorites.class);
String favoriteString = f.getFavorite(String.class);
int favoriteInteger = f.getFavorite(Integer.class);

System.out.println(favoriteString);
System.out.println(favoriteInteger);

Class<?> favoriteClass = f.getFavorite(Class.class);

System.out.printf("%s %x %s%n", favoriteString,
favoriteInteger, favoriteClass.getName());
}
}


위처럼 동적 형변환으로 런타임 안정성을 확보하는 일이다.

  • 두번째 제약은 실체화 불가타입에는 사용 할 수 없다는것

두번째 제약은 슈퍼타입토큰으로 어느정도 해결할수있지만 완벽한 해결방법은 아니다.

그럼 위에 Favorites 코드를 슈퍼타입 토큰을 사용해서 변환시키면

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

package com.github.sejoung.codetest.generics.supertype;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public abstract class TypeRef<T> {

Type type;

public TypeRef(){
Type stype = getClass().getGenericSuperclass();
if(stype instanceof ParameterizedType){
this.type = ((ParameterizedType)stype).getActualTypeArguments()[0];
}else throw new RuntimeException();
}
public int hashCode(){
return type.hashCode(); //type을 기준으로 식별(type은 Class이므로 Class레벨만 식별됨)
}
public boolean equals(Object o){
if(this == o) return true;
if(o == null || getClass().getSuperclass() != o.getClass().getSuperclass()) return false;
TypeRef<?> that = (TypeRef<?>) o;
return type.equals(that.type); //마찬가지로 두 객체 간의 type을 비교
}

}



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
31
32
33
34
35
36
37
38
39

package com.github.sejoung.codetest.generics.supertype;

import java.lang.reflect.ParameterizedType;
import java.util.*;

public class Favorites {
// 코드 33-3 타입 안전 이종 컨테이너 패턴 - 구현 (200쪽)
private Map<TypeRef<?>, Object> favorites = new HashMap<>();

public <T> T getFavorite(TypeRef<T> tr) {
if(tr.type instanceof Class<?>){ //일반클래스인 경우
return ((Class<T>)tr.type).cast(favorites.get(tr));
}else{ //제네릭타입인 경우
return ((Class<T>)((ParameterizedType)tr.type).getRawType()).cast(favorites.get(tr));
}
}

// 코드 33-4 동적 형변환으로 런타임 타입 안전성 확보 (202쪽)
public <T> void putFavorite(TypeRef<T> tr, T instance) {
favorites.put(Objects.requireNonNull(tr), instance);
}

// 코드 33-2 타입 안전 이종 컨테이너 패턴 - 클라이언트 (199쪽)
public static void main(String[] args) {
Favorites f = new Favorites();
f.putFavorite(new TypeRef<List<String>>(){}, Arrays.asList("바보","천재"));

List<String> listOfString = f.getFavorite(new TypeRef<List<String>>(){});

listOfString.forEach((s -> {
System.out.println(s);
}));


}
}


실행결과

1
2
3
4
바보
천재

Process finished with exit code 0

Favorites 코드를 위에 처럼 변환 시켜야 된다.

어렵다 토비님이 강의내용이 아주 좋았다.

비사이드 소프트에서 정리한 내용도 많은 도움이 되었습니다.

참조