alwi
alwi

Reputation: 429

How to assign values from map to fields in POJO (fields with names related to keys)?

I have a class for POJO with many fields like this:

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

I want to take values for these fields from a map where keys names are related to fields names. The data comes (from external device) in map like this:

V1=11
V2=22
...

So currently I'm defining a set of constants and use a switch to do this, like this:

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;
            ...
        }
    }
}

It seems to me like a very brute-force solution... Besides I have lots of these fields and they differ with single characters only so writing such switch block may be very error prone. Maybe someone could share a more clever mechanism (beside reflection) to solve such tasks?

EDIT - SELECTED SOLUTION

Using selected answer I've created a class:

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); }
}

And then I simply override mapProperties method.

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);
        ...
    }
}

And this is something I was looking for - a clever mechanism resulting in concise property-to-field mapping code.

Upvotes: 2

Views: 6184

Answers (4)

slim
slim

Reputation: 41223

I know you've asked for methods without Reflection, but doing it with is very simple:

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));
        }
    }

}

You could of course loop through values instead - depending on how many fields there are, and how may of them you expect to be present in an input map.

In the real world you'd need to work on this, to filter out fields of the wrong type, or fields you don't intend to be settable.

I was curious about performance so I added two more init methods -- one that doesn't use reflection at all, and another that caches the Field[] array:

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));
        }
    }
}

Profiling this over 100,000 invocations with the Netbeans profiler:

                                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

... so the performance differences are measurable but not large.


The more significant cost of using Reflection is the loss of compile-time checking, and the leaking of class internals.

Upvotes: 1

slim
slim

Reputation: 41223

I'm afraid Reflection is the only way you're going to get a programmatic translation of a string to a field or method at runtime.

Apache Commons BeanUtils provides methods like setProperty(Object bean, String name, Object value). That means that setProperty(myObj, "foo", "bar") will call myObj.setFoo("bar"). Of course it uses Reflection behind the scenes.

Or you could step back and ask why you're using this POJO model in the first place. If your program could use a Map<String,String> or a Properties object instead, this problem goes away.

Of course, it would be pretty trivial to automate the writing of your switch statement. In bash:

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

(Add your own lowercasing/whatever)

Upvotes: 2

Kai
Kai

Reputation: 1769

I think using reflection will be the cleanest and most straight forward way to achieve what you're trying to do here if the Map of values is coming from another Java class. The basic idea is to get the fields of the class and then iterate over those to get the values to assign to the new object.

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

The answer to this question may be of help to you.

Alternatively if you really don't want to use reflection and it's possible for you to read the values from a property file, you could then call the Constructor of the new Object with those values see example below.

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);
        }
    }
}

Upvotes: 0

Danilo M. Oliveira
Danilo M. Oliveira

Reputation: 728

You can always avoid a case statement with Maps and Interfaces/Abstract classes + anonymous concrete classes.

It's still ugly, but it's very flexible, since you can add more setters at runtime. It can be less ugly with lambda functions. You can devise clever ways to populate this map.

This is the only alternative I know to avoid reflection. I'm doing this in a discrete event simulator that I'm writing since reflections is slow compared to this approach. Also, with reflection you cannot obfuscate your code.

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);
}

}

Upvotes: 2

Related Questions