Our original code is something like:
Object bean = ...
Field field = ...
Object value = ... // Retrieved from Database
field.set(bean, value);
But the type of bean field may be different from type of value, e.g. our bean is an Integer
but value retrieved from db may be Byte
or Short
.
How can I write some simple code to convert boxed types without reflection exception?
Here is my solution but not as simple as my expectation:
if (field.getType().equals(Integer.class)) {
field.set(bean, Integer.valueOf(((Number) value).intValue()));
} else if (field.getType().equals(Short.class)) {
field.set(bean, Short.valueOf(((Number) value).shortValue()));
} else if (...) {
...
}
There’s no way to handle these conversion generically unless you add even more Reflection.
The best you can do, is to make these required expressions more maintainable. One thing to consider, is that with your expressions like, e.g. Short.valueOf(((Number) value).shortValue())
, the valueOf
invocation is unnecessary. That’s what auto-boxing does anyway.
Then, you can do with recent Java versions:
static final Map<Class<?>,Setter> SETTERS =Map.copyOf(Map.<Class<?>,NumericSetter>ofEntries(
Map.entry(byte.class, (field,bean,n) -> field.setByte(bean, n.byteValue())),
Map.entry(short.class, (field,bean,n) -> field.setShort(bean, n.shortValue())),
Map.entry(int.class, (field,bean,n) -> field.setInt(bean, n.intValue())),
Map.entry(long.class, (field,bean,n) -> field.setLong(bean, n.longValue())),
Map.entry(float.class, (field,bean,n) -> field.setFloat(bean, n.floatValue())),
Map.entry(double.class, (field,bean,n) -> field.setDouble(bean, n.doubleValue())),
Map.entry(Byte.class, (field,bean,n) -> field.set(bean, n.byteValue())),
Map.entry(Short.class, (field,bean,n) -> field.set(bean, n.shortValue())),
Map.entry(Integer.class, (field,bean,n) -> field.set(bean, n.intValue())),
Map.entry(Long.class, (field,bean,n) -> field.set(bean, n.longValue())),
Map.entry(Float.class, (field,bean,n) -> field.set(bean, n.floatValue())),
Map.entry(Double.class, (field,bean,n) -> field.set(bean, n.doubleValue()))
));
interface Setter {
void set(Field field, Object bean, Object value) throws IllegalAccessException;
Setter FALLBACK = Field::set;
}
interface NumericSetter extends Setter {
@Override default void set(Field field, Object bean, Object value)
throws IllegalAccessException {
setNumeric(field, bean, (Number)value);
}
void setNumeric(Field f, Object bean, Number n) throws IllegalAccessException;
}
To handle all numeric field types, whether primitive or boxed. This can be use like
SETTERS.getOrDefault(field.getType(), Setter.FALLBACK).set(field, bean, value);
to use one of the numeric conversions if applicable or just use the set
method without transformations otherwise. The latter would also throw the appropriate exception for mismatching types.
For older Java versions you could consider something like
static boolean setNumeric(Field field, Object bean, Object value)
throws IllegalAccessException {
if(!(value instanceof Number)) return false;
Number n = (Number)value;
Class<?> type = field.getType();
if(type.isPrimitive()) {
if(type == boolean.class || type == char.class) return false;
switch(type.getName().charAt(0)) {
case 'b': field.setByte(bean, n.byteValue()); break;
case 's': field.setShort(bean, n.shortValue()); break;
case 'i': field.setInt(bean, n.intValue()); break;
case 'l': field.setLong(bean, n.longValue()); break;
case 'f': field.setFloat(bean, n.floatValue()); break;
case 'd': field.setDouble(bean, n.doubleValue()); break;
default: throw new AssertionError(type);
}
}
else {
if(!Number.class.isAssignableFrom(type)
|| type.getPackage() != Object.class.getPackage())
return false;
switch(type.getSimpleName().charAt(0)) {
case 'B': field.set(bean, n.byteValue()); break;
case 'S': field.set(bean, n.shortValue()); break;
case 'I': field.set(bean, n.intValue()); break;
case 'L': field.set(bean, n.longValue()); break;
case 'F': field.set(bean, n.floatValue()); break;
case 'D': field.set(bean, n.doubleValue()); break;
default: throw new AssertionError(type);
}
}
return true;
}
This only handles the numeric types and return the success status. The caller can use it like
if(!setNumeric(field, bean, value)) {
field.set(bean, value); // non numeric or throw appropriate exception
}