우리가 많이 사용하는 형(타입) 변환(Casting)은 정적 캐스팅입니다.
UserVO userVO = (UserVO)object;
정적 캐스팅은 이미 객체(object)가 어떤 클래스인지 알고 있어야 가능합니다.
상속받은 객체라면 UpCasting(업캐스팅)으로 형(타입) 변환(Casting)하여 수퍼(부모) 클래스로 캐스팅할 수 있고 반대로 DownCasting(다운캐스팅)으로 자식(서브) 클래스로 캐스팅할 수 있습니다.
JDK 1.5부터 캐스트 구문을 대체할 수 있게 Class에서 cast() 메서드를 지원하고 있습니다.
public T cast(Object obj) {
if (obj != null && !isInstance(obj))
throw new ClassCastException(cannotCastMsg(obj));
return (T) obj;
}
Object 타입으로 전달받은 객체(obj)가 Class의 인스턴스(instance)이면 Class으로 캐시팅이 됩니다.
다음과 같이 변경할 수 있습니다.
UserVO userVO = UserVO.class.cast(object);
정적 캐스팅과 동일합니다.
그럼 동적 캐스팅은 어디에서 사용되고 어떻게 처리되는 걸까요?
첫 번째는 다양한 클래스 타입으로 생성된 객체를 맵(Map <>)을 이용하여 사용해야 하는 곳입니다.
다양한 타입으로 생성된 객체를 맵에 보관하기 위해서는 객체를 UpCasting(업캐스팅)하여 Object 타입으로 캐스팅해야 합니다. 그 이유는 다양한 타입 자체로 저장할 수 없기 때문입니다.
그리고 키(Key)는 생성된 클래스의 클래스(.class)를 사용합니다. 그 이유는 맵에서 객체를 가져올 때 키(Key)를 이용하여 캐스팅을 하기 위해서입니다.
private Map<Class<?>, Object> map = new HashMap<Class<?>, Object>();
다양한 클래스 객체를 저장하기 위해서 파라미터 타입을 제네릭(Generic)으로 선언하여 메서드를 생성합니다.
이것을 제네릭 메서드라고 합니다.
타입 매개변수(<T>)는 반드시 메서드의 반환 타입(리턴 값) 앞에 있어야 합니다.
"T"는 "Type"으로 타입을 제한하지 않아 다양한 타입이 사용될 수 있게 합니다.
public <T> void put(Class<T> objectClass, T object) {
map.put(objectClass, object);
}
키(Key)를 이용해 맵에서 Object 타입의 객체를 찾고 키(Key)를 이용하여 캐스팅하고 리턴합니다.
public <T> T get(Class<T> objectClass) {
return objectClass.cast(map.get(objectClass));
}
전체 소스입니다.
package com.home.project.test2.util;
import java.util.HashMap;
import java.util.Map;
public class ClassMap {
private Map<Class<?>, Object> map = new HashMap<Class<?>, Object>();
public <T> void put(Class<T> objectClass, T object) {
map.put(objectClass, object);
}
public <T> T get(Class<T> objectClass) {
return objectClass.cast(map.get(objectClass));
}
}
다양한 타입의 테스트를 위해 UserVO 클래스를 생성합니다. 이전에 생성된 클래스입니다.
package com.home.project.test2.vo;
public class UserVO {
private String id = "";
private String name = "";
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
다양한 타입의 테스트를 위해 OrderVO 클래스를 생성합니다.
package com.home.project.test2.vo;
public class OrderVO {
private Integer orderNumber = 0;
private String productName = "";
public Integer getOrderNumber() {
return orderNumber;
}
public void setOrderNumber(Integer orderNumber) {
this.orderNumber = orderNumber;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
}
JUnit를 이용하여 테스트 케이스를 생성하고 테스트합니다.
@Test
public void test() {
UserVO userVO = new UserVO();
userVO.setId("testid");
userVO.setName("홍길동");
OrderVO orderVO = new OrderVO();
orderVO.setOrderNumber(1234567890);
orderVO.setProductName("사과");
ClassMap classMap = new ClassMap();
classMap.put(UserVO.class, userVO);
classMap.put(OrderVO.class, orderVO);
System.out.println("User Name : " + classMap.get(UserVO.class).getName());
System.out.println("Order Product Number : " + classMap.get(OrderVO.class).getOrderNumber());
}
실행 결과입니다.
User Name : 홍길동
Order Product Number : 1234567890
동적으로 캐스팅이 되고 리턴까지 캐스팅되게 하기 위해서는 제네릭(Generic)을 사용해야 합니다. 그리고 클래스의 클래스(.class)를 이용해야 동적으로 캐스팅되는 형태를 만들 수 있습니다.
두 번째는 정보를 Object 타입으로 전달받아 생성된 객체의 멤버 필드에 값을 저장하여 사용해야 하는 곳입니다.
다시 말하면, Object 타입의 정보를 리플렉션(Reflection)을 이용하여 멤버 필드의 타입으로 캐스팅하고 저장하는 겁니다.
JUnit를 이용하여 테스트하기 위해 정보 맵을 생성 합니다.
Map<String, Object> map = new HashMap<String, Object>();
map.put("orderNumber", 1234567890);
map.put("productName", "사과");
생성된 객체의 멤버 필드들을 가져옵니다.
OrderVO orderVO2 = new OrderVO();
Field[] Fields = OrderVO.class.getDeclaredFields();
for (Field field : Fields) {
}
멤버 필드 명으로 맵에서 정보를 가져오고 멤버 필드의 타입으로 캐스팅하여 멤버 필드 값에 저장합니다.
Object fieldValue = map.get(field.getName());
try {
field.set(orderVO2, field.getType().cast(fieldValue));
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
전체 테스트 케이스 소스입니다.
@Test
public void test() {
// 주문 정보
Map<String, Object> map = new HashMap<String, Object>();
map.put("orderNumber", 1234567890);
map.put("productName", "사과");
OrderVO orderVO = new OrderVO();
Field[] Fields = OrderVO.class.getDeclaredFields();
for (Field field : Fields) {
if (map.containsKey(field.getName())) {
Object fieldValue = map.get(field.getName());
field.setAccessible(true);
try {
field.set(orderVO, field.getType().cast(fieldValue));
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
System.out.println("Order Product Number : " + orderVO2.getOrderNumber());
}
실행 결과입니다.
Order Product Number : 1234567890
여기서 고려해야 하는 사항은 객체의 멤버 필드 타입을 int, short, float, long, double, boolean, byte, char 과 같은 기본형(자료형) 타입 대신 Integer, Short, Float, Long, Double, Boolean, Byte, Character과 같은 참조형 타입인 래퍼 클래스(Wrapper class)로 해야 합니다. 그 이유는 cast() 메서드는 래퍼 클래스와 같은 클래스에서 동작하기 때문입니다. String은 래퍼 클래스입니다.
자동으로 캐스팅해서 리턴해주는 메서드들은 이런 이유로 래퍼 클래스(Wrapper class)로 리턴합니다.
이처럼 자동으로 동적 캐스팅할 수 있는 부분은 제한적입니다. 그렇지만 적절한 곳에 사용된다면 유용하게 이용할 수 있을 겁니다.