Home » Java » How to set a Number field with a different Number type using Java Reflection-Exceptionshub

How to set a Number field with a different Number type using Java Reflection-Exceptionshub

Posted by: admin February 25, 2020 Leave a comment

Questions:

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 (...) {
   ...
}
How to&Answers:

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
}