This question is similar to Reading multiple variables from an object wrapped in Option[] but in the context of Java instead of Scala.
Suppose I have a method that returns an Optional<Address>
.
I need to extract multiple fields from the wrapped address, say getCity()
, getCountry()
, and getZIP()
. If the address is not present, some default values should be used. Then the logic should continue with the resulting values.
The question is what is the best and most idiomatic way of doing this. I can think of the following three:
1) if-else
City city;
Country country;
ZIP zip;
Optional<Address> optAddr = getAddress();
if (optAddr.isPresent()) {
Address addr = optAddr.get();
city = addr.getCity();
country = addr.getCountry();
zip = addr.getZIP();
}
else {
city = null;
country = Country.getUSA();
zip = ZIP.DEFAULT;
}
// ... use city, country, and zip
Pros:
- No extra objects created - most efficient variant.
- Default-cases are grouped together.
Cons:
- Verbose.
2) Multiple chains
Optional<Address> optAddr = getAddress();
City city = optAddr.map(Address::getCity()
.orElse(null);
Country country = optAddr.map(Address::getCountry)
.orElseGet(Country::getUSA);
ZIP zip = optAddr.map(Address::getZIP)
.orElse(ZIP.DEFAULT);
// ... use city, country, and zip
Note that here the logic is slightly different since default value apply not only when the address is missing, but also when the corresponding address field is null
.
But that's not an issue in my case.
Pros:
- Concise.
- Variables are immediately initialized to a value.
- Closer to how it is done with a single field.
Cons:
- Default values are spread apart.
- Chained calls to
map
potentially create intermediate objects (but in an idiomatic way). - Maybe unidiomatic use of multiple chains on a single
Optional
?
3a) Wrap default values in an object
private Address createDefaultAddress() {
Address addr = new Address();
addr.setCity(null);
addr.setCountry(Country.getUSA());
addr.setZIP(ZIP.DEFAULT);
return addr;
}
Address addr = getAddress().orElseGet(this::createDefaultAddress);
City city = addr.getCity();
Country country = addr.getCountry();
ZIP zip = addr.getZIP();
// ... use city, country, and zip
Pros:
- Clarity.
Cons:
- Fields are packed into an
Address
just for extracting them immediately afterwards.
3b) Wrap default values in a constant
As suggested by @HariMenon and @flakes, the an Address
with default values can be stored in a constant and reused for each call to reduce the overhead.
One could also initialize that 'constant' lazily:
private Address defaultAddress;
private Address getDefaultAddress() {
if (defaultAddress == null) {
defaultAddress = new Address();
defaultAddress.setCity(null);
defaultAddress.setCountry(Country.getUSA());
defaultAddress.setZIP(ZIP.DEFAULT);
}
return defaultAddress;
}
Address addr = getAddress().orElseGet(this::getDefaultAddress);
// ... same as 3.1
Bonus question:
Suppose I have full control of the getAddress()
method, and I know that all usages would be similar to this example, but the default values vary.
Should I change it to return null
instead of Optional
to better accomodate the if-else
solution?