Reputation: 51
Hello I am looking for a way to reduce cognitive complexity of method which has multiple if statements. All are basically nullchecks for each item of object and is required.
Code snippet:
private void assignFruit(FruitBasket1<?> fruit1, FruitBasket2 fruit2) {
if (fruit1.name != null) {
fruit2.setName(fruit1.name.toString());
}
if (fruit1.number != null) {
fruit2.setNumber(fruit1.number.toString());
}
if (fruit1.color != null) {
fruit2.setColor(fruit1.color.toString());
}
if (fruit1.region != null) {
fruit2.setRegion(fruit1.region.toString());
}
}
I have tried to use switch case but since its only null check didnt find feasible, also tried ternary operator. Is there a simpler way to do this as although in the code snippet its only few fields there are almost 20 fields which need be null checked. What are the other possibilities to do this simple manner without modifying the POJO class and managing in this method itself.
Note: There was an error in the code in the way values are set
Upvotes: -1
Views: 502
Reputation: 7871
You can use Optional<>
.
// static imported Optional.ofNullable()
private void assignFruit(FruitBasket1<?> fruit1, FruitBasket2 fruit2) {
ofNullable(fruit1.name).ifPresent(fruit2::setName);
ofNullable(fruit1.number).ifPresent(fruit2::setNumber);
ofNullable(fruit1.color).ifPresent(fruit2::setColor);
ofNullable(fruit1.region).ifPresent(fruit2::setRegion);
}
You can improve it a little bit more with a static import of ofNullable()
.
I'm not sure, but you need to change the type somehow?
Also that is fluent possible with Optional
.
// static imported Optional.ofNullable()
private void assignFruit(FruitBasket1<?> fruit1, FruitBasket2 fruit2) {
ofNullable(fruit1.name).map(Object::toString).ifPresent(fruit2::setName);
ofNullable(fruit1.number).map(Object::toString).ifPresent(fruit2::setNumber);
ofNullable(fruit1.color).map(Object::toString).ifPresent(fruit2::setColor);
ofNullable(fruit1.region).map(Object::toString).ifPresent(fruit2::setRegion);
}
Upvotes: 0
Reputation: 40047
First, if these attributes are not supposed to be null, how do they get to be null in the first place? And if they are null, why not just copy them? Without fully understanding your use case, it doesn't make much sense.
Especially when one wonders what the getter
would return.
In any event, presuming that you never want to set the fruit in a basket if it is null, then enforce that invariant in the setMethod
itself.
class FruitBasket2 {
public void setName(FruitBasket1<?> fb) {
if(fb.name != null) {
this.name = fb.name;
}
}
...
...
}
Consider that if someone were to use your class FruitBasket
, why make the user check for null before setting the value?
Upvotes: 0
Reputation: 1117
If you are willing to add an additional dependency, MapStruct is a very good choice to map Beans/Dtos in a very readable manner.
You could simply add an interface and map these two objects.
In order to ignore null values, you need to set the property, nullValuePropertyMappingStrategy
to NullValuePropertyMappingStrategy.IGNORE
Thus, the final code looks like:
@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE )
public interface SimpleSourceDestinationMapper {
FruitBasket2 sourceToDestination(FruitBasket1 fruitBasket1);
}
Since, FruitBasket1
and FruitBasket2
have same attributes, it will automatically map them all except the null values.
Note: It is just a more readable version of the mapping. In terms of complexity, it is the same.
Guides to Mapstruct: https://www.baeldung.com/mapstruct
Upvotes: 3
Reputation: 9658
Yes, you can also define the following method:
private static <T> void setIfNotNull(T value, Consumer<T> cons) {
if (value != null) {
cons.accept(value);
}
}
And use it as follows:
setIfNotNull(fruit1.name, fruit2::setName);
setIfNotNull(fruit1.number, fruit2::setNumber);
setIfNotNull(fruit1.color, fruit2::setColor);
setIfNotNull(fruit1.region, fruit2::setRegion);
Upvotes: 0
Reputation: 11941
You can extract that logic by using method references for the getters and setters.
For example you can extract Fruit::getColor
to a Function<Fruit, String>
and squeeze the setter into a BiConsumer<Fruit, String>
.
Then you can match each getter to a setter using some generic Pair
such as a simple
record Pair<R,L> (R a, L b){}
Then you can store these Pair
s into a List
and iterate over them:
List<Pair<Function<Fruit, String>, BiConsumer<Fruit, String>>> getterSetterPair = new ArrayList<>();
//"connect" getters and setters here
getterSetterPair.add(new Pair<>(Fruit::getColor, Fruit::setColor));
getterSetterPair.add(new Pair<>(Fruit::getName, Fruit::setName));
Fruit fruit1 = null, fruit2 = null;
for(Pair<Function<Fruit, String>, BiConsumer<Fruit, String>>pair:getterSetterPair) {
Function<Fruit, String> getter = pair.a();
BiConsumer<Fruit, String> setter = pair.b();
//this is doing the logic you did before
String value = getter.apply(fruit1);
if(value != null) {
setter.accept(fruit2, value);
}
}
In general, I would also advise you to use a proven library such as MapStruct (see @hermit's) answer for that.
Upvotes: 0