如何将值从地图分配到POJO中的字段(名称与键相关的字段)?

时间:2017-05-17 09:04:44

标签: java

我有一个POJO课,有很多这样的字段:

class DataObj {
    private String v1 = null;
    private String v2 = null;
    ...

我想从一个地图中获取这些字段的值,其中键名与字段名相关。 data来自(来自外部设备)地图,如下所示:

V1=11
V2=22
...

所以目前我正在定义一组常量并使用开关来执行此操作,如下所示:

private static final String V1 = "V1";
private static final String V2 = "V2";
...

DataObj(Map<String, String> data) {
    for (String key : data.keySet()) {
        String value = data.get(key);
        switch (key) {
            case V1:
                v1 = value;
                break;
            case V2:
                v2 = value;
                break;
            ...
        }
    }
}

在我看来,这是一个非常强力的解决方案...除了我有很多这些字段,它们只有单个字符不同,所以写这样的开关块可能非常容易出错。也许有人可以分享一个更聪明的机制(旁边的反思)来解决这些任务?

编辑 - 选择解决方案

使用选定的答案我创建了一个课程:

abstract class PropertyMapper<T> {
    private Map<String, Setter<T>> setters = new HashMap<>();

    abstract void mapProperties();

    public PropertyMapper() {
        mapProperties();
    }

    protected void updateBean(Map<String, T> map) {
        for (String key : map.keySet()) {
            setField(key, map.get(key));
        }
    }

    protected void mapProperty(String property, Setter<T> fieldAssignment) {
        setters.put(property, fieldAssignment);
    }

    protected interface Setter<T> { void set(T o); }

    private void setField(String s, T o) { setters.get(s).set(o); }
}

然后我只是覆盖mapProperties方法。

class DataObj extends PropertyMapper<String> {
    private String v1 = null;
    private String v2 = null;
    ...

    DataObj(Map<String, String> data) {
        updateBean(data);
    }

    @Override
    void mapProperties() {
        mapProperty("V1", o -> v1 = o);
        mapProperty("V2", o -> v2 = o);
        ...
    }
}

这是我正在寻找的东西 - 一种巧妙的机制,可以产生简洁的属性到字段映射代码。

4 个答案:

答案 0 :(得分:2)

您始终可以使用Maps和Interfaces / Abstract类+匿名具体类来避免使用case语句。

它仍然很难看,但它非常灵活,因为你可以在运行时添加更多的setter。 lambda函数可能不那么难看。您可以设计巧妙的方法来填充此地图。

这是我知道避免反思的唯一选择。我在一个离散的事件模拟器中这样做,因为与这种方法相比,反射很慢。此外,通过反射,您无法模糊代码。

public class PojoTest {

public int a;
public int b;
public int c;

private Map<String, Setter> setters = new HashMap<String, Setter>();

public PojoTest() {
initSetters();
}

public void set(Map<String, Integer> map) {
for (String s : map.keySet()) {
    setField(s, map.get(s));
}
}

public String toString() {
return a + ", " + b + ", " + c;
}

public static void main(String[] args) {
PojoTest t = new PojoTest();
Map<String, Integer> m = new HashMap<>();
m.put("a", 1);
m.put("b", 2);
m.put("c", 3);

t.set(m);

System.out.println(t);
}

private void setField(String s, Object o) {
setters.get(s).set(o);
}

private void initSetters() {
setters.put("a", new Setter() {
    @Override
    public void set(Object o) {
    a = (Integer) o;
    }
});

setters.put("b", new Setter() {
    @Override
    public void set(Object o) {
    b = (Integer) o;
    }
});

setters.put("c", new Setter() {
    @Override
    public void set(Object o) {
    c = (Integer) o;
    }
});
}

private static interface Setter {

public void set(Object o);
}

}

答案 1 :(得分:2)

我担心反射是您在运行时将字符串编程转换为字段或方法的唯一方式。

Apache Commons BeanUtils提供了setProperty(Object bean, String name, Object value)等方法。这意味着setProperty(myObj, "foo", "bar")会调用myObj.setFoo("bar")。当然它在幕后使用Reflection。

或者你可以退后一步,问一下你为什么要首先使用这个POJO模型。如果您的程序可以使用Map<String,String>Properties对象,则此问题就会消失。

当然,自动编写switch语句非常简单。在bash:

while read fieldname; do
    cat << EOF
case("${fieldname}"):
   this.$fieldname = data.get(key);
   break;
EOF
done

(添加你自己的小套/其他)

答案 2 :(得分:1)

我知道你已经要求没有反射的方法,但是这样做非常简单:

public class PojoWithValues {

    private String v1;
    private String v2;
    private String v3;

    public void configureWithReflection(Map<String, String> values)
            throws IllegalAccessException, IllegalArgumentException {

        Field[] fields = PojoWithValues.class.getDeclaredFields();

        for (Field field : fields) {
            String name = field.getName();
            field.set(this, values.get(name));
        }
    }

}

您当然可以循环浏览values - 取决于有多少字段,以及您希望它们在输入映射中的显示方式。

在现实世界中,您需要处理此问题,过滤掉错误类型的字段或您不打算设置的字段。

我对性能感到好奇所以我添加了两个init方法 - 一个根本不使用反射,另一个缓存Field[]数组:

public class PojoWithValues {

    private String v1;
    private String v2;
    private String v3;

    private Field[] FIELDS;

    public PojoWithValues() {
        this.FIELDS = PojoWithValues.class.getDeclaredFields();
    }

    public void configureWithoutReflection(Map<String, String> values) {
        v1 = values.get("v1");
        v2 = values.get("v2");
        v3 = values.get("v3");
    }

    public void configureWithReflection(Map<String, String> values)
            throws IllegalAccessException, IllegalArgumentException {
        Field[] fields = PojoWithValues.class.getDeclaredFields();

        for (Field field : fields) {
            String name = field.getName();
            field.set(this, values.get(name));
        }
    }

    public void configureWithCache(Map<String, String> values) throws IllegalAccessException, IllegalArgumentException {
        for (Field field : FIELDS) {
            String name = field.getName();
            field.set(this, values.get(name));
        }
    }
}

使用Netbeans探查器分析超过100,000次调用:

                                Total time      Total time (CPU) Invocations
testInitWithReflection ()       507 ms (36.4%)  472 ms (36.9%)   100,000
testInitWithCache ()            480 ms (34.5%)  441 ms (34.5%)   100,000
testInitWithoutReflection ()    458 ms (32.9%)  419 ms (32.7%)   100,000

......所以性能差异是可衡量的,但不是很大。

使用Reflection的更大成本是编译时检查的丢失以及类内部的泄漏。

答案 3 :(得分:0)

我认为如果值的Map来自另一个Java类,使用反射将是实现你在这里尝试做的最干净,最直接的方法。基本思想是获取类的字段,然后迭代这些字段以获取要分配给新对象的值。

Class<?> objClass = obj.getClass();
Field[] fields = objClass.getDeclaredFields();
//loop over array of fields and create your new object

The answer这个问题可能会对你有所帮助。

或者如果您真的不想使用反射,并且您可以从属性文件中读取值,则可以调用新的构造函数具有这些值的对象参见下面的示例。

public class App {

    public static void main( String[] args ){

        App app = new App();
        app.createNewObjectFromProperties();

    }

    private void createNewObjectFromProperties() {
        Properties prop = new Properties();
        InputStream input = null;

        try {

            String filename = "your.properties";
            input = App.class.getResourceAsStream(filename);
            if(input==null){
                System.out.println("File not found: " + filename);
                return;
            }

            prop.load(input);

            String v1 = prop.getProperty("V1");
            String v2 = prop.getProperty("V2");
            NewObject newObject = new NewObject(v1, v2);

        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    private class NewObject {
        private String v1;
        private String v2;

        public NewObject(String v1, String v2) {
            this.v1 = v1;
            this.v2 = v2;
            System.out.println(v1);
            System.out.println(v2);
        }
    }
}