在使用反射时,如何在Java中映射和强制转换未知类型



我使用反射将getter从一个类映射到另一个类的setter,即stuts1使用的表单类主要用于显示文本(字符串),后端使用的纯Java对象以其特定类型保存值。我目前已经在getter和setter之间进行了映射,这很容易,但我在混合类型方面遇到了麻烦。我使用setter中的参数类型来查看预期的内容,因此我需要从getter中确定对象的类型,并将其转换为预期的类型。

例如

HomePageForm  ->   HomePageData 
Name="Alexei" ->   String name; (Maps correctly) 
Age="22"      ->   int age;     (Needs casting from string to int and visa-vera in reverse)

以下是我到目前为止的代码

/**
     * Map the two given objects by reflecting the methods from the mapTo object and finding its setter methods.  Then 
     * find the corresponding getter method in  the mapFrom class and invoke them to obtain each attribute value.  
     * Finally invoke the setter method for the mapTo class to set the attribute value.  
     * 
     * @param mapFrom The object to map attribute values from
     * @param mapTo   The object to map attribute values to
     */
    private void map(Object mapFrom, Object mapTo) {
        Method [] methods = mapTo.getClass().getMethods();
        for (Method methodTo : methods) {
            if (isSetter(methodTo)) {
                try {
                    //The attribute to map to setter from getter
                    String attName = methodTo.getName().substring(3);
                    //Find the corresponding getter method to retrieve the attribute value from
                    Method methodFrom = mapFrom.getClass().getMethod("get" + attName, new Class<?>[0]);
                    //If the methodFrom is a valid getter, set the attValue
                    if (isGetter(methodFrom)) {
                        //Invoke the getter to get the attribute to set
                        Object attValue = methodFrom.invoke(mapFrom, new Object[0]);
                        Class<?> fromType = attValue.getClass();
                        //Need to cast/parse type here
                        if (fromType != methodTo.getParameterTypes()[0]){
                            //!!Do something to case/parse type!!
                        } //if
                        //Invoke the setter to set the attribute value
                        methodTo.invoke(mapTo, attValue);
                    } //if
                } catch (Exception e) {
                    Logger.getLogger(Constants.APP_LOGGER).fine("Exception in DataFormMappingService.map: "
                                                              + "IllegalArgumentException" + e.getMessage());
                    continue;
                }
            } //if
        } //for
    } //map

提前感谢,阿列克谢蓝。

我不是反思中的英雄,但我的猜测是int是一个原始数据类型,而您的attValueObject类型。

您可以尝试将年龄类型更改为Integer,以便将其转换为Object

是的,@Molske很准确。问题是将int返回值提升为Integer,然后进行比较:

if (fromType != methodTo.getParameterTypes()[0])

我刚刚对你的代码进行了快速测试,得到了一个异常:

java.lang.AssertionError: expected:<class java.lang.Integer> but was:<int>

如果将fromTypemethodFrom.getReturnType()进行比较,则代码应该可以工作:

if (methodFrom.getReturnType() != methodTo.getParameterTypes()[0])

这是我使用的测试:

@Test
public void testException() throws Exception {
    Foo foo = new Foo();
    Bar bar = new Bar();
    for (Method methodTo : Bar.class.getMethods()) {
        if (methodTo.getName().startsWith("set")) {
            String attName = methodTo.getName().substring(3);
            Method methodFrom = Foo.class.getMethod("get" + attName);
            if (methodFrom != null && methodFrom.getName().startsWith("get")) {
                Object attValue = methodFrom.invoke(foo);
                // this ignores the auto-boxing issues
                assertEquals(methodFrom.getReturnType(),
                    methodTo.getParameterTypes()[0]);
                methodTo.invoke(bar, attValue);
            }
        }
    }
}
private static class Foo {
    public int getFoo() { return 1; }
}
private static class Bar {
    public void setFoo(int i) { System.out.println("i = " + i); }
}

我最终实现了一个更好的解决方案:

    /**
     * Map to given objects taking into account the inclusion and exclusion sets.
     * 
     * @param mapFrom The object to map attribute values from
     * @param mapTo   The object to map attribute values to
     */
    private void map(Object mapFrom, Object mapTo)
    {
        setMapFilter(mapFrom, mapTo);
        for (Method sMethod : filterMap.keySet())
        {            
            try
            {
               //Find the corresponding getter method to retrieve the attribute value from
                Method gMethod = filterMap.get(sMethod);
                //Invoke the getter to get the attribute to set
                Object attValue = gMethod.invoke(mapFrom, new Object[0]);
                //Get the mapping types and check their compatibility, if incompatible convert attValue type to toType
                Class<?> fromType = gMethod.getReturnType();
                Class<?> toType   = sMethod.getParameterTypes()[0];
                if(!toType.isAssignableFrom(fromType) && Objects.isNotNull(attValue))
                {
                    attValue = parseType(attValue, toType);
                } //if
                //Invoke the setter to set the attribute value
                sMethod.invoke(mapTo, attValue);
            }
            catch (IllegalArgumentException e)
            {
                Logger.getLogger(Constants.APP_LOGGER).fine("Exception in DataFormMappingService.map: "
                                                          + "IllegalArgumentException " + e.getMessage());
                continue;
            }
            catch (IllegalAccessException e)
            {
                Logger.getLogger(Constants.APP_LOGGER).fine("Exception in DataFormMappingService.map: "
                                                          + "IllegalAccessException " + e.getMessage());
                continue;
            }
            catch (InvocationTargetException e)
            {
                Logger.getLogger(Constants.APP_LOGGER).fine("Exception in DataFormMappingService.map: "
                                                          + "InvocationTargetException " + e.getMessage());
                continue;
            }
        } //for each
    } //map
    /**
     * Refactored method to parse an object, attValue, from it's unknown type to the type expected.
     * 
     * @param attValue The attribute value to parse
     * @param type     The type expected/to convert to
     * @return         The attribute value in the expected type, null otherwise
     */
    private Object parseType(Object attValue, Class<?> type)
    {
        Object newAttValue = null;
        if (isLiteral(type))
        {
            newAttValue = attValue.toString();
        }
        else if (isByte(type))
        {
            newAttValue = Byte.valueOf(attValue.toString());
        }
        else if (isInt(type))
        {
            newAttValue = Integer.valueOf(attValue.toString());
        }
        else if (isShort(type))
        {
            newAttValue = Short.valueOf(attValue.toString());
        }
        else if (isLong(type))
        {
            newAttValue = Long.valueOf(attValue.toString());
        }
        else if (isFloat(type))
        {
            newAttValue = Float.valueOf(attValue.toString());
        }
        else if (isDouble(type))
        {
            newAttValue = Double.valueOf(attValue.toString());
        }
        else if (isBoolean(type))
        {
            newAttValue = Boolean.valueOf(attValue.toString());
        } //if-else if*
        return newAttValue;
    } //parseType

它比我最初的解决方案更干净,但本质上,在映射时,过滤器是由要映射的方法构建的,这些方法只是循环通过然后映射。parse方法只检查类型,并在Object.toString()上使用valueOf方法,该方法适用于标准Java类型。

干杯,

阿列克谢蓝。

最新更新