2

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.

alwi
  • 431
  • 6
  • 21
  • 1
    What is wrong with reflection? It was the first solution that came to my mind. – mdewit May 17 '17 at 09:10
  • I was just wondering if there is a non-reflection based, more straight forward solution for such tasks that I'm missing / not aware of and maybe obvious for someone else... – alwi May 17 '17 at 09:14
  • 1
    Unless you use reflections, you are going to need to map key names to field names by hand, which means you need to adapt that code whenever you are adding fields. There probably are other "clever" ways that don't involve reflections, but they will likley be just slight variants of what are doing now. – kapex May 17 '17 at 09:18

4 Answers4

2

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

}
  • This is interesting. I'll check this out. Thanks. – alwi May 17 '17 at 09:31
  • 1
    I love a `Map>`, but in this case I don't really see how it improves on a giant `switch` statement. Sometimes dumb is best. – slim May 17 '17 at 09:38
  • 1
    As I said, you can modify map at run time (add/replace/remove entries), while the switch is a hard coded structure. You can initialize the map in a dynamic way according to a configuration file or parameters, you can serialize the map in a object file on the disk, and so on. By relying on polymorphism instead of conditional structures, you gain a lot of flexibility. – Danilo M. Oliveira May 17 '17 at 09:46
  • Yes, and as soon as you want it to support those dynamic things, this becomes a good idea -- and no sooner. The whole question is about writing to fields -- which are not dynamic. – slim May 17 '17 at 09:54
  • It's just an alternative way to switch/reflection, as the OP asked. I'm just endorsing its advantages, but I'm aware that it's more complicated than a hard coded switch. – Danilo M. Oliveira May 17 '17 at 10:01
  • Well, the answer that there nothing better in java than reflection is good enough, but I'll gladly analyze any alternatives cause I already came upon this problem quite many times in the past. The above is definitely worth considering for me, e.g. because the code is more concise (when using lamdas it's also quite readable) then switch with all those boiler plate breaks. Same goes to Apache Commons BeanUtils. – alwi May 17 '17 at 11:04
  • 1
    If Apache Commons does use reflection behind the scenes, you need to consider this fact if you care about performance. For example, in a game where you need to do the task as rapid as possible to obtain a decent frame rate, you definitely should avoid using reflection for creating enemies, bullets, particles, etc. – Danilo M. Oliveira May 17 '17 at 11:41
  • If you're determined to use Reflection, you could reflect during initialisation, caching the results. Similar to Spring which does a lot of reflection during wiring, but not much thereafter. But premature optimisation is premature. – slim May 17 '17 at 13:50
2

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)

slim
  • 40,215
  • 13
  • 94
  • 127
  • 1
    If you wrote this part in Groovy you'd get easy access to this kind of thing. Groovy and Java mix well. Not really appropriate for an answer, because it's not what you asked, but worth mentioning. – slim May 17 '17 at 09:34
  • @alwi - I don't follow your logic here. You can change the internals of your class to store its data in a map without in any way changing the API it presents to a GUI/widget/processor/anything. – slim May 17 '17 at 10:27
  • "why you're using this POJO model in the first place"? If I stay with Map model in my module and want to e.g. display the data in any kind of (ordered) GUI/widget (table or set of fields) or if I want to process/analyze/validate values in any way then I'd have go through this step eventually... It is not needed only if the data is just passed through, but it's not the case. It already came to processing & view part of application. So each data element needs to be handled in its specific way although all they came in same Map. – alwi May 17 '17 at 10:27
  • ... and if every field "needs to be handled in its specific way" then you can't avoid a long-winded explicit solution. – slim May 17 '17 at 10:39
1

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.

slim
  • 40,215
  • 13
  • 94
  • 127
0

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);
        }
    }
}
Community
  • 1
  • 1
Kai
  • 1,709
  • 1
  • 23
  • 36