https://www.youtube.com/watch?v=01sdXvZSjcI&list=PLv-xDnFD-nnmof-yoZQN8Fs2kVljIuFyC&index=16
예시
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class SuperTypeToken02 {
static class Sup<T>{
T value;
}
//super 타입의 지네릭 정보는 런타임 시 지워지지 않고, 바이트 코드에 남아있다.
static class Sub extends Sup<String>{ }
public static void main(String[] args) throws Exception {
Sup<String> sup = new Sup<>();
sup.value = "asd";
//java.lang.Object
System.out.println(sup.getClass().getDeclaredField("value").getType());
//지네릭 정보를 런타임에 읽을 수 있는 상황예시
Sub sub = new Sub();
Type type = sub.getClass().getGenericSuperclass();
ParameterizedType pType = (ParameterizedType) type;
//타입이 런타임에 지워지지 않는다.
//java.lang.String
System.out.println(pType.getActualTypeArguments()[0]);
Sup<String> sup2 = new Sup<String>() {};
Type type2 = sup2.getClass().getGenericSuperclass();
ParameterizedType pType2 = (ParameterizedType) type2;
//java.lang.String
//바이트 코드에 정보가 남아 있기 때문에 아무리 중첩되도 정보를 다 가져올 수 있다.
System.out.println(pType2.getActualTypeArguments()[0]);
Sup sup3 = new Sup<List<Map<Class<String>,Number>>>() {};
Type type3 = sup3.getClass().getGenericSuperclass();
ParameterizedType pType3 = (ParameterizedType) type3;
//java.lang.String
System.out.println(pType3.getActualTypeArguments()[0]);
}
}
class java.lang.Object
class java.lang.String
class java.lang.String
java.util.List<java.util.Map<java.lang.Class<java.lang.String>, java.lang.Number>>
지네릭 정보는 기본적으로 하위 호환성을 위해 컴파일 시 type eraser에 의해 지네릭 타입은 전부 Object로 바뀌고 그 자리에 형변환이 추가된다.
디컴파일러에 따라 저 형변환이 안보이는 경우가 있어 class 파일로 직접 확인
위 처럼 지네릭 타입 정보는 런타임 시 제거되고 그자리에 적절한 형변환 코드가 남는다.
모든 지네릭 정보가 다 지워지는 것은 아니다. 상위 타입 지네릭 정보는 지워지지 않는다.
그래서 예제 코드에서 상위 타입인 지네릭 정보를 읽어올 수 있던 것이다.
익명 클래스로 객체 선언과 동시에 인스턴스화 하면, 대상 클래스가 서브 타입이 된다. 즉, 하위 타입이 된다. 따라서 대상 타입의 지네릭 정보가 안지워진 것이다.
bin 폴더에 보면 위와 같이 익명클래스 클래스 코드가 존재하고, 타입 정보가 남아있다.
SuperTypeToken
익명 클래스 인스턴스를 인자로 넘겨 지네릭 정보를 얻는 방식을 슈퍼타입토큰이라 한다.
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class SuperTypeToken03 {
static class TypesafeMap{
Map<TypeReference<?>, Object> map = new HashMap<>();
<T> void put(TypeReference<T> tr, T v) {
map.put(tr, v);
}
<T> T get(TypeReference<T> tr) {
//단순 타입이면 문제없지만, 컨테이너 타입이면 캐스팅을 못한다.
//<String> ok , <List<String>> not ok
if(tr.type instanceof Class<?>)
return ((Class<T>)tr.type).cast(map.get(tr));
else
return ((Class<T>)((ParameterizedType)tr.type).getRawType()).cast(map.get(tr));
}
}
static class TypeReference<T>{
//지네릭 정보까지 포함한 정보를 저장한 필드
Type type;
public TypeReference() {
Type stype = this.getClass().getGenericSuperclass();
//ParameterizedType인 경우는 <T> 에 정보가 들어와있는 것
if(stype instanceof ParameterizedType) {
this.type = ((ParameterizedType) stype).getActualTypeArguments()[0];
}
else throw new RuntimeException();
}
//IDE에서 제공한 자동 생성 코드
@Override
public int hashCode() {
return Objects.hash(type);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
//getClass() != obj.getClass() 여기에 걸려 false를 리턴해 현재 동작하지 않는다.
//우리는 현재 클래스가 아닌 클래스속 Type 이 목적이다.
if (obj == null || getClass().getSuperclass() != obj.getClass().getSuperclass())
return false;
//이 클래스의 익명 클래스를 사용하기 때문에 슈퍼클래스가 익명클래스가 된다.
TypeReference<?> other = (TypeReference<?>) obj;
return Objects.equals(type, other.type);
}
}
public static void main(String[] args) {
TypesafeMap typesafeMap = new TypesafeMap();
//되는 경우
TypeReference<String> tr = new TypeReference<String>() {};
typesafeMap.put(tr, "하나둘");
System.out.println(typesafeMap.get(tr));
//안되는 경우, equals()메서드를 오버라이딩 하지 않을 경우, 물리적 주소 값을 비교하게 되어 불가능
typesafeMap.put(new TypeReference<Integer>() {}, 123);
System.out.println(typesafeMap.get(new TypeReference<Integer>() {}));
//컬렉션에서 요소가 같은 값인지 판단하는 기준은 Object.equals()메소드 결과다.
//따라서 아래와 같은 경우는 다른 key로 인식하여 값을 가져오지 못한다.
typesafeMap.put(new TypeReference<List<String>>() {}, Arrays.asList("하나","둘","셋"));
System.out.println(typesafeMap.get(new TypeReference<List<String>>() {}));
typesafeMap.put(new TypeReference<List<List<String>>>() {}, Arrays.asList(Arrays.asList("하나"),Arrays.asList("둘"),Arrays.asList("셋")));
System.out.println(typesafeMap.get(new TypeReference<List<List<String>>>() {}));
//get() 메서드 추가 설명
TypeReference<String> typeReference1 = new TypeReference<String>() {};
TypeReference<List<String>> typeReference2 = new TypeReference<List<String>>() {};
System.out.println(typeReference1.type);
System.out.println(typeReference1.type instanceof ParameterizedType);
System.out.println(typeReference2.type);
System.out.println(typeReference2.type instanceof ParameterizedType);
System.out.println(((ParameterizedType)typeReference2.type).getRawType());
}
}
하나둘
123
[하나, 둘, 셋]
[[하나], [둘], [셋]]
class java.lang.String
false
java.util.List<java.lang.String>
true
interface java.util.List
TypesafeMap 개선
package javabasic.toby;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class SuperTypeToken03 {
static class TypesafeMap{
Map<Type, Object> map = new HashMap<>();
<T> void put(TypeReference<T> tr, T v) {
map.put(tr.type, v);
}
<T> T get(TypeReference<T> tr) {
//단순 타입이면 문제없지만, 컨테이너 타입이면 캐스팅을 못한다.
//<String> ok , <List<String>> not ok
if(tr.type instanceof Class<?>)
return ((Class<T>)tr.type).cast(map.get(tr.type));
else
return ((Class<T>)((ParameterizedType)tr.type).getRawType()).cast(map.get(tr.type));
}
}
static class TypeReference<T>{
//지네릭 정보까지 포함한 정보를 저장한 필드
Type type;
public TypeReference() {
Type stype = this.getClass().getGenericSuperclass();
//ParameterizedType인 경우는 <T> 에 정보가 들어와있는 것
if(stype instanceof ParameterizedType) {
this.type = ((ParameterizedType) stype).getActualTypeArguments()[0];
}
else throw new RuntimeException();
}
}
public static void main(String[] args) {
TypesafeMap typesafeMap = new TypesafeMap();
typesafeMap.put(new TypeReference<String>() {}, "하나둘");
System.out.println(typesafeMap.get(new TypeReference<String>() {}));
typesafeMap.put(new TypeReference<Integer>() {}, 123);
System.out.println(typesafeMap.get(new TypeReference<Integer>() {}));
typesafeMap.put(new TypeReference<List<String>>() {}, Arrays.asList("하나","둘","셋"));
System.out.println(typesafeMap.get(new TypeReference<List<String>>() {}));
typesafeMap.put(new TypeReference<List<List<String>>>() {}, Arrays.asList(Arrays.asList("하나"),Arrays.asList("둘"),Arrays.asList("셋")));
System.out.println(typesafeMap.get(new TypeReference<List<List<String>>>() {}));
}
}
key로 Type을 사용하면, 따로 논리적 동치성을 보장하기 위한 코드가 필요없다.
'개발 > 자바(JAVA)' 카테고리의 다른 글
ParameterizedTypeReference(SuperTypeToken) (0) | 2023.06.30 |
---|---|
컴포지트 패턴 연습 - 2 (0) | 2023.04.16 |
컴포지트 패턴 연습 - 1 (0) | 2023.04.13 |
기본형 배열 List로 변환하기 (0) | 2023.03.10 |
객체 지향 - 6 (0) | 2023.02.03 |