如何使用 Java 反射设置具有不同数字类型的数字字段
How to set a Number field with a different Number type using Java Reflection
我们的原始代码是这样的:
Object bean = ...
Field field = ...
Object value = ... // Retrieved from Database
field.set(bean, value);
但是bean字段的类型可能与值的类型不同,例如我们的 bean 是一个 Integer
但从数据库中检索到的值可能是 Byte
或 Short
.
如何编写一些简单的代码来转换装箱类型而没有反射异常?
这是我的解决方案,但并不像我预期的那么简单:
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 (...) {
...
}
除非您添加更多的反射,否则无法通用地处理这些转换。
您能做的就是使这些必需的表达式更易于维护。需要考虑的一件事是,使用您的表达方式,例如Short.valueOf(((Number) value).shortValue())
,valueOf
调用是不必要的。这就是自动装箱的作用。
然后,您可以使用最新的 Java 版本:
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;
}
处理所有数字字段类型,无论是原始的还是盒装的。这可以像
一样使用
SETTERS.getOrDefault(field.getType(), Setter.FALLBACK).set(field, bean, value);
在适用的情况下使用其中一种数字转换,否则只使用 set
方法而不进行转换。后者还会为不匹配的类型抛出适当的异常。
对于较旧的 Java 版本,您可以考虑类似
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;
}
这仅处理数字类型和 return 成功状态。调用者可以这样使用
if(!setNumeric(field, bean, value)) {
field.set(bean, value); // non numeric or throw appropriate exception
}
我们的原始代码是这样的:
Object bean = ...
Field field = ...
Object value = ... // Retrieved from Database
field.set(bean, value);
但是bean字段的类型可能与值的类型不同,例如我们的 bean 是一个 Integer
但从数据库中检索到的值可能是 Byte
或 Short
.
如何编写一些简单的代码来转换装箱类型而没有反射异常?
这是我的解决方案,但并不像我预期的那么简单:
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 (...) {
...
}
除非您添加更多的反射,否则无法通用地处理这些转换。
您能做的就是使这些必需的表达式更易于维护。需要考虑的一件事是,使用您的表达方式,例如Short.valueOf(((Number) value).shortValue())
,valueOf
调用是不必要的。这就是自动装箱的作用。
然后,您可以使用最新的 Java 版本:
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;
}
处理所有数字字段类型,无论是原始的还是盒装的。这可以像
一样使用SETTERS.getOrDefault(field.getType(), Setter.FALLBACK).set(field, bean, value);
在适用的情况下使用其中一种数字转换,否则只使用 set
方法而不进行转换。后者还会为不匹配的类型抛出适当的异常。
对于较旧的 Java 版本,您可以考虑类似
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;
}
这仅处理数字类型和 return 成功状态。调用者可以这样使用
if(!setNumeric(field, bean, value)) {
field.set(bean, value); // non numeric or throw appropriate exception
}