Java Getters to HashMap - PullRequest
       19

Java Getters to HashMap

1 голос
/ 15 апреля 2020

У меня есть файл класса для геттеров и сеттеров. Для другой операции мне нужны эти переменные , для которых установлены значения (т. Е. Не ноль) в 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;
}
....

Я использовал ReflectionToStringBuilder для построения строки для другой вариант использования.

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

Возможно ли сгенерировать HashMap, который бы выглядел примерно так (value будет тем, что установлено с помощью метода setter)?

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

Ответы [ 5 ]

2 голосов
/ 15 апреля 2020

Сделайте это следующим образом:

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

Вывод:

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

Вы упомянули, что вы уже знакомы с ReflectionToStringBuilder. Я надеюсь, вы также знакомы с String::split. Остальная часть логи c прямолинейна.

1 голос
/ 15 апреля 2020

Если мое понимание верно, следующий исполняемый класс делает то, что вы ожидаете.

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

}

1 голос
/ 15 апреля 2020

Вы можете достичь этого с помощью отражения:

    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());
0 голосов
/ 15 апреля 2020

Правильный способ сделать это - использовать Introspector. Вот Java 8+ способ сделать это:

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

}

Разница между этим и другими предлагаемыми решениями заключается в том, что согласно спецификации Javabeans свойства определяются не полями , но с помощью аксессоров (геттеров и / или сеттеров). Средства доступа к данному свойству "foo" записываются в PropertyDescriptor с именем "foo", которое ссылается на геттер (если он присутствует) через pd.getReadMethod(), а установщик (если присутствует) - pd.getWriteMethod(). Полностью допустимо, чтобы свойство Javabeans имело

  1. только для чтения (только для получения)
  2. только для записи (только для установки)
  3. чтение-запись (оба )

По соглашению свойство Javabeans должно поддерживаться полем с тем же именем, что и имя свойства, но это всего лишь соглашение. Javabeans может поддерживаться другим полем, другим методом или вообще без поля.

Другое отличие состоит в том, что если в вашем классе есть суперклассы, этот метод также будет возвращать свои свойства компонента, вплоть до (но не в том числе) Object.class.

0 голосов
/ 15 апреля 2020

Если я понял, что вы имеете в виду, ваше решение должно быть следующим:

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

Метод toMap() принимает объект для преобразования в карту и карту, которая будет содержать результат. Метод также включает в себя поля из суперклассов. Каждое имя поля включает каноническое имя класса / абстрактного класса, к которому оно принадлежит. Включенный основной метод выводит следующее:

{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}

Пропущенное исключение не может быть вызвано, поскольку доступ к полям безопасен.

Вы можете использовать метод toMap() в toMap() метод вашего класса тоже.

Как видите, этот метод принимает логическое значение, чтобы запретить включение полей из суперклассов. В этом случае он не включает каноническое имя класса в ключах карты.

Обратите внимание, что если obj - массив, карта будет выглядеть примерно так:

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