Reputation: 1262
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
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
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
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
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
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
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