Reputation: 6646
I want to write a generic DTO updater, which updates some of it's properties. My object looks like this:
class Person {
String firsName;
String lastName;
Integer age;
setter/getter
}
I have an enum which defines which fields can be overridden:
enum OverriddenType {
FIRST_NAME,
LAST_NAME,
AGE
}
Instead of doing a "hardcoded" setter like this...
public void override(Person person, Map<OverriddenType,Object> newValuesForProperties) {
newValuesForProperties.forEach((type,value)) -> {
if(type == OverriddenType.FIRST_NAME) {
person.setFirstName((String)value);
} else if(type == OverriddenType.LAST_NAME) {
person.setLastName((String)value);
} else if(type == OverriddenType.AGE) {
person.setAge((Integer)value);
}
});
}
... I would like to use some Java 8 Function/Consumer features to define the setters in each enum. I tried something like this:
enum OverriddenType {
FIRST_NAME(p -> p.setFirstName(???????)), //don't know what to set here since the value is not here
LAST_NAME(p -> p.setLastName(???????)),
AGE(p -> p.setAge(???????));
Consumer<Person> consumer;
OverriddenType(Consumer<Person> consumer) {
this.consumer = consumer;
}
public Consumer<Person> getConsumer();
}
But as you can see I cannot pass the dynamic value here. Also don't know how the logic would look like, but I would imagine something like this:
public void override(Person person, Map<OverriddenType,Object> newValuesForProperties) {
newValuesForProperties.forEach((type,value)) ->
type.getConsumer().accept(person, value) // this doesn't exist, but I can imagine some additional parameter passing
);
}
Can you provide me a solution for this? Can it work? I am afraid that the incoming value is a dynamic therefore it won't work for enums..
Upvotes: 1
Views: 5210
Reputation: 159086
A setter method is a BiConsumer<T,U>
, where T
is the object instance, and U
is the value.
enum OverriddenType {
FIRST_NAME((person, value) -> person.setFirsName((String) value)),
LAST_NAME ((person, value) -> person.setLastName((String) value)),
AGE ((person, value) -> person.setAge((Integer) value));
private final BiConsumer<Person, Object> setter;
private OverriddenType(BiConsumer<Person, Object> setter) {
this.setter = setter;
}
public void override(Person person, Object value) {
this.setter.accept(person, value);
}
}
public void override(Person person, Map<OverriddenType, Object> newValuesForProperties) {
newValuesForProperties.forEach((type, value) -> type.override(person, value));
}
Upvotes: 4
Reputation: 265154
You need a BiConsumer
which can consume the target object and the new value:
enum OverriddenType {
FIRST_NAME((p, v) -> p.setFirstName(v)),
LAST_NAME((p, v) -> p.setLastName(v)),
AGE((p, v) -> p.setAge(v));
BiConsumer<Person> consumer;
OverriddenType(BiConsumer<Person, Object> consumer) {
this.consumer = consumer;
}
public BiConsumer<Person> getConsumer();
}
(If your setter accepts an Object
, you may even use method references:
OBJECT(Person::setObject)
)
Instead of exposing the consumer directly, consider defining a public assign
or set
method in your enum and make the consumer invisible to the outside (information hiding):
public void assign(final Person target, final Object newValue) {
this.consumer.accept(target, newValue);
}
then call as FIRST_NAME.assign(person, "new name")
.
Upvotes: 1
Reputation: 308001
By definition a Consumer
only ever takes one argument.
Since you need to be passed both the object to manipulate and the new value, you have to use an interface that allows two arguments to be passed. Luckily there's the BiConsumer
interface for that.
BiConsumer<Person, String> personFirstNameSetter = Person::setFirstName;
Note that the use of a method reference here is roughly equivalent to writing (p, fn) -> p.setFirstName(fn)
.
However, this introduces a slight problem, because you need to know (and specify) the type of the parameter and your enum would like to have mixed types (the first two would return BiConsumer<Person,String>
and the last one probably BiConsumer<Person,Integer>
.
Upvotes: 2