Type Token Generic

ํƒ€์ž…์˜ ์•ˆ์ •์„ฑ์„ ํ™•๋ณดํ•˜๊ธฐ ์œ„ํ•˜์—ฌ ํด๋ž˜์Šค์˜ ์‹๋ณ„ ํ† ํฐ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ๋ฒ•์„ ๋งํ•œ๋‹ค.

์ฆ‰ ํŠน์ • ํด๋ž˜์Šค ์ •๋ณด๋ฅผ ๋„˜๊ฒจ์„œ ์•ˆ์ •์„ฑ์„ ๋…ธ๋ฆฌ๋Š” ๊ธฐ๋ฒ•์„ TypeToken ์ด๋ผ๊ณ  ํ•œ๋‹ค.

ํƒ€์ž…ํ† ํฐ์„ ์‚ฌ์šฉํ•˜๋Š” TypeSaftyMap

static class TypeSaftyMap {
  Map <Class<?>, Object> map = new HashMap<>();

  <T> void put(Class<T> clazz, T value) {
    map.put(clazz, value);
  }

  <T> get(Class<T> clazz) {
    return clazz.cast(map.get(clazz));
  }
}

์ด ์ฝ”๋“œ๋Š” ๊ฐ ๋ฉ”์„œ๋“œ T ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋Š” Class ๋ฅผ ์‹๋ณ„ํ•˜๋Š” ์ œ๋„ค๋ฆญ์ด๋ฉฐ ๋™์‹œ์— clazz ๋ฅผ ํ† ํฐ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค.

์ด๋ฅผ ํ™œ์š”ํ•˜๋Š” ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

TypeSaftyMap map = new TypeSaftyMap();

map.put(String.class, "String");
map.put(Integer.class, 3);
map.put(List.class, Arrays.asList(1, 2, 3));

System.out.println(String.class);
System.out.println(Integer.class);
System.out.println(List.class);

Type Erasure

์ œ๋„ค๋ฆญ์€ ํƒ€์ž… ์†Œ๊ฑฐ์ž (Type Erasure) ์— ์˜ํ•ด ์ž์‹ ์˜ ํƒ€์ž… ์š”์†Œ ์ •๋ณด๋ฅผ ์‚ญ์ œํ•œ๋‹ค.

๋•Œ๋ฌธ์— ์•„๋ž˜์™€ ๊ฐ™์ด ์‹ค ํƒ€์ž… ๋งค๊ฐœ๋ณ€์ˆ˜ ํ˜•ํƒœ์˜ ์„ ์–ธ์„ ์ปดํŒŒ์ผ ๊ณผ์ •์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณ€๊ฒฝํ•œ๋‹ค.

Before

List<String> list = new ArrayList<>();

After

ArrayList list = new ArrayList();

ํƒ€์ž… ํŒŒ๋ผ๋ฉ”ํ„ฐ๋Š” ๋ฌผ๋ก ์ด๊ณ  ์Šˆํผํด๋ž˜์Šค์œ„ ํƒ€์ž… ํŒŒ๋ผ๋ฉ”ํ„ฐ ๋ฐ ํ•ด๋‹น ํด๋ž˜์Šค์˜ ์ •์˜๋œ ๋ชจ๋“  ํƒ€์ž… ํŒŒ๋ผ๋ฉ”ํ„ฐ๊ฐ€ ์ง€์›Œ์ง„๋‹ค.

์ œ๋„ค๋ฆญ์—์„œ ์‚ฌ์šฉํ•˜๋Š” Type Variable ์„ ์ œ๊ฑฐํ•˜๊ณ  ๊ธฐ๋ฐ˜ํƒ€์ž… (๋ชจ๋“  ๊ฐ์ฒด์˜ ๊ธฐ๋ฐ˜ํƒ€์ž…์ธ Object) ์„ ์‚ฝ์ž…ํ•œ๋‹ค.

J2SE 5 ์ด์ „ ์ œ๋„ค๋ฆญ์ด ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ์ฝ”๋“œ์˜ ํ˜ธํ™˜์„ฑ์„ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•ด์—ฌ ์ง€์›๋˜๋Š” ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ธฐ๋Šฅ ์ค‘์˜ ํ•˜๋‚˜์ด๋‹ค.

์ด๋Š” ์ œ๋„ค๋ฆญ ์ถœํ˜„ ์ด์ „ ํ•˜์œ„ ํ˜ธํ™˜์„ฑ (Java SE 1.4 ์ดํ•˜) ์„ ์œ„ํ•ด์„œ Byte Code ๋กœ ์ „ํ™˜ํ•˜๋Š” ๊ณผ์ •์—์„œ ์ œ๋„ค๋ฆญ ํƒ€์ž…์„ ์ œ๊ฑฐํ•œ๋‹ค.

์œ„ ์ฝ”๋“œ๋ฅผ ์˜ˆ์‹œ๋กœ ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑํ•ด๋ณด์ž

map.put(List.class, Arrays.asList(1, 2, 3));
map.put(List.class, Arrays.asList("1", "2", "3"));

์œ„ ์ฝ”๋“œ์—์„œ๋Š” ๋ฌธ์ œ์ ์€ ๋™์ผํ•œ ํด๋ž˜์Šค์— ๋‹ค๋ฅธ ๊ฐ’์„ ๋„ฃ์œผ๋ฉด ๋ฎ์–ด ์”Œ์—ฌ์ง„๋‹ค.
๋˜ํ•œ ๊ฐ’์— ๋“ค์–ด๊ฐ€๋Š” ๋‘๊ฐœ์˜ ๊ฐ์ฒด๋Š” ์„œ๋กœ ๋‹ค๋ฅธ ์ œ๋„ค๋ฆญ ๊ฐ’์ด๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค๋ฅธ ํƒ€์ž…์ด๋‹ค.

์—„๋ฐ€ํžˆ ๋งํ•˜๋ฉด List.class ๊ฐ€ ์•„๋‹Œ List<Integer>.class List<String>.class ์ด๋‹ค.
์ด ๋‘๊ฐœ๋ฅผ ๋™์ผํ•˜๊ฒŒ ์•„๋ž˜์™€ ๊ฐ™์ด ๋Œ€์ž…ํ•˜๋ฉด ์—๋Ÿฌ์ด๋‹ค.

map.put(List<Integer>.class, Arrays.asList(1, 2, 3));
map.put(List<String>.class, Arrays.asList("1", "2", "3"));

์ด์™€ ๊ฐ™์ด ํƒ€์ž… ์†Œ๊ฑฐ์— ์˜ํ•ด ๋Ÿฐํƒ€์ž„ ์‹œ์ ์— ํƒ€์ž…์•ˆ์ •์„ฑ์ด ๋ณด์žฅ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— Super Type Token ์ด ์ถœํ˜„ํ•˜์˜€๋‹ค.

ํƒ€์ž… ๊ตฌ์ฒดํ™” (Type Reification)

Java ์™€๋Š” ๋‹ค๋ฅด๊ฒŒ C# ์—์„œ๋Š” ์ œ๋„ค๋ฆญ ์‚ฌ์šฉ์‹œ ํƒ€์ž…์†Œ๊ฑฐ๊ฐ€ ์•„๋‹Œ ํƒ€์ž… ๊ตฌ์ฒดํ™” ๋ฐฉ์‹์„ ํ†ตํ•ด ์ œ๋„ค๋ฆญ์„ ๊ตฌํ˜„ํ–ˆ๋‹ค. ์ด๋Š” ์ปดํŒŒ์ผ์‹œ Byte Code ๋ณ€ํ™˜์‹œ์—๋„ ํƒ€์ž…์ด ์†Œ๊ฑฐ๊ฐ€ ๋˜์ง€ ์•Š๊ณ  ์‹ค ํƒ€์ž…์ •๋ณด๋ฅผ Byte Code ์•ˆ์— ๋ณด์กดํ•˜๋Š”๊ฒƒ์ธ๋ฐ

์ด๋กœ ์ธํ•ด C# ์€ ์ œ๋„ค๋ฆญ ์ถœํ˜„ ์ด์ „์˜ ํ•˜์œ„ํ˜ธํ™˜์„ฑ์„ ํฌ๊ธฐํ•˜๋Š” ๋ฐฉ์‹์„ ์ฑ„ํƒํ•œ ๊ฒƒ์ด๋‹ค.

DANGER

Bounded Type ์ œ์•ฝ ์กฐ๊ฑด์€ ํƒ€์ž… ์†Œ๊ฑฐ์— ํฌํ•จ๋˜์ง€ ์•Š๋Š”๋‹ค.

Super Type Token

์ผ๋ฐ˜์ ์œผ๋กœ ์ œ๋„ค๋ฆญ ํƒ€์ž…์€ ์ปดํŒŒ์ผ ์‹œ์  (Byte Code ๋กœ ๋ณ€ํ™˜) ์— Type Erasure ์— ์˜ํ•ด ํƒ€์ž… ์ •๋ณด๊ฐ€ ์†Œ๊ฑฐ๋˜๋Š”๋ฐ

๋ถ€๋ชจ ํด๋ž˜์Šค (Super Class) ๋ฅผ ์ œ๋„ค๋ฆญ ํƒ€์ž…์„ ํ†ตํ•ด ์ƒ์†์ด ๋˜๋ฉด ์ปดํŒŒ์ผ (Byte Code ๋กœ ๋ณ€ํ™˜) ํ•ด๋„ ์ œ๋„ค๋ฆญ ์ •๋ณด (์‹ค ํƒ€์ž… ์ธ์ž) ๊ฐ€ ๋ณด์กด๋˜์–ด ๋Ÿฐํƒ€์ž„ ์‹œ์— Reflection ์„ ์ด์šฉํ•˜์—ฌ ํƒ€์ž… ์ •๋ณด๋ฅผ ์–ป์–ด์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

// ์ œ๋„ค๋ฆญ ๋ถ€๋ชจ ํด๋ž˜์Šค
class Parent<T> {
  T value;
}

// ์ •์ ์œผ๋กœ ์ •์˜๋œ ๋ถ€๋ชจ๋ฅผ ์ƒ์†ํ•˜๋Š” ์„œ๋ธŒ ํด๋ž˜์Šค
class Child extends Parent<String> {
  ...
}

๋ฆฌํ”Œ๋ ‰์…˜์„ ์ด์šฉํ•œ ๋ถ€๋ชจ์˜ ์ œ๋„ค๋ฆญ ํƒ€์ž… ๊ตฌํ•˜๊ธฐ

Child child = new Child();

// ๋ถ€๋ชจ์˜ ์ œ๋„ค๋ฆญ ํด๋ž˜์Šค ์ •๋ณด๋ฅผ ์–ป์Œ
Type type = child.getClass().getGenericSuperclass();

// ํƒ€์ž…์œผ๋กœ๋ถ€ํ„ฐ ํŒŒ๋ผ๋ฉ”ํ„ฐ ํƒ€์ž…์„ ๋‹ค์‹œ ์–ป์Œ
ParameterizedType paramType = (ParameterizedType) type;

// ํŒŒ๋ผ๋ฉ”ํ„ฐ ํƒ€์ž…์˜ ์ธ์ž๋ฅผ ํ™•์ธํ•˜๋ฉด ๊ทธ ์•ˆ์— ์ œ๋„ค๋ฆญ ํƒ€์ž…์ด ๋“ค์–ด์žˆ์Œ
System.out.println(paramType.getActualTypeArguments()[0]);  // String

์œ„๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์ถ”์ƒํ™”ํ•œ TypeReference ํด๋ž˜์Šค๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.

TypeReference Class

abstract class TypeReference<T> {
  Type type;

  public TypeReference() {
    Type parentType = getClass.getGenericSuperclass();
    this.type = ((ParameterizedType) parentType).getActualTypeArguments()[0];
  }
}

์ž‘์„ฑ๋œ TypeReference ํด๋ž˜์Šค๋ฅผ ์ด์šฉํ•˜์—ฌ TypeSaftyMap ์„ ๊ฐœ์„ ํ•œ๋‹ค.

๊ฐœ์„ ๋œ TypeSaftyMap Class

class TypeSaftyMap {
  Map<TypeReference<?>, Object> map = new HashMap<>();

  <T> void put(TypeReference<T> tRef, T value) {
    map.put(tRef, value);
  }

  <T> get(TypeReference<T> tRef) {
    return ((Class<T>) tRef.type).cast(map.get(tRef));
  }
}

์œ„ ์ฝ”๋“œ๋Š” TypeReference ํด๋ž˜์Šค๊ฐ€ ์ธ์Šคํ„ด์Šค ํ˜•์œผ๋กœ get ๋ฉ”์„œ๋“œ์— ์‚ฝ์ž…๋˜์–ด ์ฐธ์กฐํ˜•๊ฐ’ ํ† ํฐ์œผ๋กœ ๋˜์–ด ๋ฒ„๋ ธ๋‹ค.

์ด๋ฅผ ๋ณด์•ˆํ•˜๋ ค๋ฉด TypeReference ํด๋ž˜์Šค๊ฐ€ ํ•ด์‹œ๋งต์—์„œ ์ธ์Šคํ„ด์Šคํ˜•์œผ๋กœ ์‹๋ณ„๋˜์ง€ ์•Š๊ณ  ํƒ€์ž…์œผ๋กœ ์‹๋ณ„ ๋˜๋„๋ก hashCode ์™€ equals ๋ฅผ ์žฌ์ •์˜ ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

hashCode ์™€ equals ๋ฅผ ์žฌ์ •์˜ํ•œ TypeReference

abstract class TypeReference<T> {
  Type type;

  public TypeReference() {
      Type parentType = getClass().getGenericSuperclass();

      if (parentType instanceof ParameterizedType) {
          this.type = ((ParameterizedType) parentType).getActualTypeArguments()[0];
      } else throw new RuntimeException();
  }

  public int hashCode() {
      return type.hashCode();
  }

  public boolean equals(Object obj) {
      if (this == obj) return true;
      if (obj == null || getClass().getSuperclass() != obj.getClass().getSuperclass()) return false;

      TypeReference<?> that = (TypeReference<?>) obj;

      return type.equals(that.type);
  }

  public String toString() {
      return type.getTypeName();
  }
}

์ œ๋„ค๋ฆญ ์ต๋ช… ์ธ์Šคํ„ด์Šค ํ™•์ธ

TypeSaftyMap map = new TypeSaftyMap();
map.put(new TypeReference<Integer>(){}, 3);
map.put(new TypeReference<String>(){}, "ABC");

// java.lang.Integer
System.out.println(new TypeReference<Integer>(){});

// java.lang.String
System.out.println(new TypeReference<String>(){});

// 3
System.out.println(map.get(new TypeReference<Integer>(){}));
// ABC
System.out.println(map.get(new TypeReference<String>(){}));

//์—๋Ÿฌ๋ฐœ์ƒ
System.out.println(map.get(new TypeReference<List<Integer>>(){}));
//์—๋Ÿฌ๋ฐœ์ƒ
System.out.println(map.get(new TypeReference<List<String>>(){}));

์„œ๋ธŒ ์ œ๋„ค๋ฆญ ์ต๋ช…ํด๋ž˜์Šค ๋ณ€๊ฒฝ

class TypeSaftyMap {
  Map<TypeReference<?>, Object> map = new HashMap<>();

  <T> void put(TypeReference<T> typeRef, T value) {
    map.put(typeRef, value);
  }

  <T> get(TypeReference<T> typeRef) {
    // ์ผ๋ฐ˜ ํด๋ž˜์Šค
    if (typeRef.type instanceof Class<?>) {
      return ((Class<T>) typeRef).cast(map.get(typeRef));
    } else {
      return ((Class<T>) ((ParameterizedType) typeRef.type).getRawType()).case(map.get(typeRef));
    }
  }
}

์ต๋ช… ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ๋Š” ์ œ๋„ค๋ฆญ ํƒ€์ž… ํŒŒ๋ผ๋ฉ”ํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•œ ์šฉ๋„ ์ด๋‹ค.

์„œ๋ธŒ ์ œ๋„ค๋ฆญ ํด๋ž˜์Šค ํ™•์ธ

TypeSaftyMap map = new TypeSaftyMap();
map.put(new TypeReference<List<Integer>>(){}, Array.asList(1,2,3));
map.put(new TypeReference<List<String>>(){}, Array.asList("a","b","c"));

//[1,2,3]
System.out.println(map.get(new TypeReference<List<Integer>>(){}));
//["a","b","c"]
System.out.println(map.get(new TypeReference<List<String>>(){}));