Betafish
Betafish

Reputation: 1262

Java Getters to HashMap

I have a class file for getters and setters. For another operation, I need these variable for which value are set (i.e. not null) to a HashMap.

private double Amount;
private Date abcDate;
private double Profit;
private boolean start;

public double getAmount() {
    return Amount;
}
public void setAmount(double amount) {
    Amount = amount;
}
public Date getAbcDate() {
    return abcDate;
}
public void setAbcDate(Date abcDate) {
    this.abcDate = abcDate;
}
....

I have used the ReflectionToStringBuilder for building string for another use-case.

public HashMap<String, Double> toHashMap(){
    Object myself = this;
    System.out.println(myself);
    ReflectionToStringBuilder builder = new ReflectionToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) {
    return null;
} 

Is it possible to generate a HashMap which would look something like this (The value would be what is set using the setter method)?

Key     Value
Amount  1000
abcDate 21/2/2020
Profit  200
start   true

Upvotes: 1

Views: 3113

Answers (5)

Arvind Kumar Avinash
Arvind Kumar Avinash

Reputation: 79425

Do it as follows:

import java.time.LocalDate;
import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

public class Item {
    private double Amount;
    private LocalDate abcDate;
    private double Profit;
    private boolean start;

    public Item(double amount, LocalDate abcDate, double profit, boolean start) {
        Amount = amount;
        this.abcDate = abcDate;
        Profit = profit;
        this.start = start;
    }

    @Override
    public String toString() {
        return ReflectionToStringBuilder.toString(this, ToStringStyle.MULTI_LINE_STYLE, true, true);
    }

    public Map<String, String> toHashMap() {
        Map<String, String> map = new LinkedHashMap<String, String>();
        String str = toString();
        str = str.substring(str.indexOf("[") + 2).replace("]", "");
        String[] tokens = str.split("\n");
        for (String token : tokens) {
            String[] fv = token.trim().split("=");
            if (!fv[1].equals("<null>")) {
                map.put(fv[0], fv[1]);
            }
        }
        return map;
    }

    public static void main(String[] args) {
        Item item1 = new Item(1000, LocalDate.of(2020, 2, 21), 200, true);
        System.out.println(item1.toHashMap());

        Item item2 = new Item(1000, null, 200, true);
        System.out.println(item2.toHashMap());
    }
}

Output:

{Amount=1000.0, abcDate=2020-02-21, Profit=200.0, start=true}
{Amount=1000.0, Profit=200.0, start=true}

You have mentioned that you are already familiar with ReflectionToStringBuilder. I hope, you are also familiar with String::split. Rest of the logic is straight forward.

Upvotes: 2

Sean Patrick Floyd
Sean Patrick Floyd

Reputation: 299048

The correct way of doing this is to utilize the Introspector. Here's a Java 8+ way to do that:

public static Map<String, Object> beanPropertyMap(final Object instance) {
    requireNonNull(instance, "instance may not be null");
    try {
        return Arrays.stream(
            Introspector.getBeanInfo(instance.getClass(), Object.class) 
                        // introspect the class hierarchy below Object.class
                        .getPropertyDescriptors())
                     .map(pd -> invokeGetter(pd, instance))
                     .filter(t -> t.getValue() != null)
                     .collect(Collectors.toMap(Tuple::getKey, Tuple::getValue));
    } catch (IntrospectionException e) {
        throw new IllegalStateException(e);
    }
}

private static Tuple invokeGetter(final PropertyDescriptor propertyDescriptor, final Object instance) {
    String key = propertyDescriptor.getName();
    Object value;
    try {
        value = propertyDescriptor.getReadMethod().invoke(instance);
    } catch (IllegalAccessException | InvocationTargetException e) {
        // you may want to log something here
        value = null;
    }
    return new Tuple(key, value);
}

private static class Tuple {

    private final String key;
    private final Object value;

    Tuple(final String key, final Object value) {
        this.key = key;
        this.value = value;
    }

    public String getKey() {
        return key;
    }

    public Object getValue() {
        return value;
    }

}

The difference between this and the other proposed solutions is that according to the Javabeans specification, properties are defined not by fields, but by accessors (getters and / or setters). The accessors of a given property "foo" are captured in a PropertyDescriptor with the name "foo", which references the getter (if present) through pd.getReadMethod() and the setter (if present) through pd.getWriteMethod(). It is completely valid for a Javabeans property to be

  1. read-only (getter only)
  2. write-only (setter only)
  3. read-write (both)

By convention, a Javabeans property should be backed up by a field with the same name as the property name, but that's just a convention. Javabeans can be backed by a different field, a different method, or no field at all.

The other difference is that if your class has superclasses, this method will also return their bean properties, up to (but not including) Object.class.

Upvotes: 1

Salvatore Giamp&#224;
Salvatore Giamp&#224;

Reputation: 82

If i understood what you mean, your solution should be the following:

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ReflectionToMap {
    private static void toMap(Object obj, Map<String, Object> map, boolean includeSuperclass) {
        Class<?> cls = obj.getClass();
        try {

            if (cls.isArray()) {
                for (int i = 0; i < Array.getLength(obj); i++)
                    map.put("" + i, Array.get(obj, i));
            } else {
                while (cls != null && !cls.isInterface()) {
                    Field[] fields = cls.getDeclaredFields();
                    for (Field field : fields) {
                        if (Modifier.isStatic(field.getModifiers()))
                            continue;
                        boolean accessible = field.isAccessible();
                        field.setAccessible(true);
                        Object value = field.get(obj);
                        field.setAccessible(accessible);
                        if (includeSuperclass)
                            map.put(cls.getCanonicalName() + "." + field.getName(), value);
                        else
                            map.put(field.getName(), value);
                    }
                    if (includeSuperclass)
                        cls = cls.getSuperclass();
                    else
                        return;
                }
            }
        } catch (Exception e) {
            System.err.println("Something gone wrong...");
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("world");

        Map<String, Object> map = new HashMap<>();
        toMap(list, map, true);
        System.out.println(map);

        map.clear();
        toMap(list, map, false);
        System.out.println(map);
    }
}

The method toMap() takes an object to convert to map and the map that will contains the result. The method includes fields from superclasses, too. Each field name, includes the canonical name of the class/abstract class, which it belongs to. The included main method outputs the following:

{java.util.ArrayList.elementData=[Ljava.lang.Object;@70dea4e, java.util.ArrayList.size=2, java.util.AbstractList.modCount=2}
{size=2, elementData=[Ljava.lang.Object;@70dea4e}

The catched exception cannot be thrown, because the access to fields is safe.

You can use the toMap() method in the toMap() method of your class, too.

As you see, this method accepts a boolean to inhibit the inclusion of fields from superclasses. In this case it does not include the canonical name of the class in map keys.

See that if obj is array, the map will be something like:

1 -> obj1
2 -> obj2
3 -> obj3
...and so on...

Upvotes: 0

nic
nic

Reputation: 139

If my understanding is correct, the following executable class does what you expect.

public class Reflection {

public static void main(String[] args) throws Exception {

    var bean = new Bean();
    bean.setAmount(1000);
    bean.setAbcDate(new Date());
    bean.setProfit(200);
    bean.setStart(true);

    var result = new HashMap<String, Object>();
    for (var f : Bean.class.getDeclaredFields()) {
        result.put(f.getName(), f.get(bean));
    }

    System.out.println(result);
}

public static class Bean {
    private double Amount;
    private Date abcDate;
    private double Profit;
    private boolean start;

    public void setAmount(double amount) {
        Amount = amount;
    }

    public void setAbcDate(Date abcDate) {
        this.abcDate = abcDate;
    }

    public void setProfit(double profit) {
        Profit = profit;
    }

    public void setStart(boolean start) {
        this.start = start;
    }
}

}

Upvotes: 1

CodeScale
CodeScale

Reputation: 3314

You can achieve that through reflection :

    Field[] fields = this.getClass().getFields();

    Map<String, String> map = new HashMap<String, String>();

     for(Field f : fields)
            map.put(f.getName(),f.get(this).toString());

Upvotes: 1

Related Questions