Допустим, у меня есть 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. И я хочу использовать его для разных типов не только строк, но и целых чисел и т. д.
Можно ли решить каким-то простым способом, не создавая специальный конвертер и не связывая его с каждой записью?
@Sweeper нет. Та же ошибка, связанная с невозможностью приведения объекта к целому/строке...




Используйте что-то вроде
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, с условным копированием.
Спасибо, почти так) Я просто хотел иметь общую логику пропуска обновлений с нулевыми значениями.
@Holger, это красиво (стыдно использовать бульдозер для достижения того же в некоторых местах)
Я бы даже рекомендовал создать функцию, подобную copy, с той же сигнатурой, что и copyWhenNonNull, чтобы иметь возможность последовательно использовать ссылки на методы, но это может быть просто делом вкуса...
@Marco13 Marco13 существует целый зоопарк способов копирования, таких как copyOrDefault(getter, setter, defaultValue) или copyWhenMatching(getter, setter, predicate) и т. д.
Я знаю, что у вас уже есть ответ, но для тех, кому понадобится что-то подобное в будущем: я разработал небольшую библиотеку для этого контекста — данные.
Вот пример, который показывает некоторые из его особенностей:
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"
]
*/
Замените
Objectна?. Это работает?