Общий способ обновления pojos через геттеры и сеттеры

Допустим, у меня есть POJO с геттерами и сеттерами разных типов. Я хочу написать какой-то общий алгоритм для обновления данных от одного к другому, основанный только на определении геттеров и сеттеров через лямбда-выражения. Я пытаюсь создать это таким образом

private static final Map<Function<Entity, Object>, BiConsumer<Entity, Object>> ACCESSORS = new HashMap
        <Function<Entity, Object>, BiConsumer<Entity, Object>>() {{
    put(Entity::getAreaCode, Entity::setAreaCode);
}});

Затем я просматриваю все записи, применяя к ним целевой объект, и если результат геттера не равен нулю, я хочу применить соответствующий сеттер для другого объекта.

Но это не сработает, потому что Object нельзя преобразовать в String. И я хочу использовать его для разных типов не только строк, но и целых чисел и т. д.

Можно ли решить каким-то простым способом, не создавая специальный конвертер и не связывая его с каждой записью?

Замените Object на ?. Это работает?

Sweeper 21.01.2019 17:50

@Sweeper нет. Та же ошибка, связанная с невозможностью приведения объекта к целому/строке...

user_x 21.01.2019 18:02
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
8
2
837
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Используйте что-то вроде

private static final List<BiConsumer<Entity,Entity>> ACCESSORS =
    Collections.unmodifiableList(Array.asList(
        (src,dst) -> dst.setAreaCode(src.getAreaCode()),
        (src,dst) -> dst.setOtherProperty(src.getOtherProperty())
        /* etc */
));

Затем вы можете просмотреть список и применить каждую операцию к двум объектам, например

static final void copyAll(Entity src, Entity dst) {
    ACCESSORS.forEach(op -> op.accept(src, dst));
}

Ключевым моментом является то, что фактический тип значения свойства обрабатывается в каждом BiConsumer, но больше не является частью общей подписи, и поэтому его не нужно объявлять для ACCESSORS. Он даже более эффективен, так как может обрабатывать примитивные типы данных без накладных расходов.

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

Вы можете интегрировать логику «копировать только тогда, когда не является нулевым» с помощью общего вспомогательного метода:

private static final List<BiConsumer<Entity,Entity>> ACCESSORS =
    Collections.unmodifiableList(Arrays.asList(
        copyWhenNonNull(Entity::getAreaCode, Entity::setAreaCode),
        copyWhenNonNull(Entity::getOtherProperty, Entity::setOtherProperty)
        /* etc */
));
private static <E,V> BiConsumer<E,E> copyWhenNonNull(
    Function<? super E, ? extends V> getter, BiConsumer<? super E, ? super V> setter) {
    return (src,dst) -> {
        V value = getter.apply(src);
        if (value != null) setter.accept(dst, value);
    };
}

Метод copyAll не меняется. Это даже позволяет смешивать безусловное копирование свойств, которые никогда не могут быть null, с условным копированием.

Спасибо, почти так) Я просто хотел иметь общую логику пропуска обновлений с нулевыми значениями.

user_x 21.01.2019 18:13

@Holger, это красиво (стыдно использовать бульдозер для достижения того же в некоторых местах)

Eugene 21.01.2019 21:57

Я бы даже рекомендовал создать функцию, подобную copy, с той же сигнатурой, что и copyWhenNonNull, чтобы иметь возможность последовательно использовать ссылки на методы, но это может быть просто делом вкуса...

Marco13 22.01.2019 17:50

@Marco13 Marco13 существует целый зоопарк способов копирования, таких как copyOrDefault(getter, setter, defaultValue) или copyWhenMatching(getter, setter, predicate) и т. д.

Holger 22.01.2019 17:52

Я знаю, что у вас уже есть ответ, но для тех, кому понадобится что-то подобное в будущем: я разработал небольшую библиотеку для этого контекста — данные.

Вот пример, который показывает некоторые из его особенностей:

class Person {
  //getters + setters omitted for brevity
  private String firstName;
  private String lastName;
}

class PersonDTO {
  //getters + setters + empty constructor omitted for brevity
  private String firstName;
  private String lastName;
}

  //the mutable API defines a mapping process by multiple getter-setter steps
  Mapper<Person, PersonDTO> mapper = Datus.forTypes(Person.class, PersonDTO.class).mutable(PersonDTO::new)
      .from(Person::getFirstName).into(PersonDTO.setFirstName)
      .from(Person::getLastName)
      .given(Objects::nonNull, ln -> ln.toUpperCase()).orElse("fallback")
      .into(PersonDTO::setLastName)
      .from(/*...*/).into(/*...*/)
      .build();

  Person person = new Person();
person.setFirstName("firstName");
    person.setLastName(null);
    PersonDTO personDto = mapper.convert(person);
/*
    personDto = PersonDTO [
        firstName = "firstName",
        lastName = "fallback"
    ]
*/
    person.setLastName("lastName");
    personDto = mapper.convert(person);
/*
    personDto = PersonDTO [
        firstName = "firstName",
        lastName = "LASTNAME"
    ]
*/

Другие вопросы по теме