从包裹在 Java 中的对象中提取多个字段可选
Extract multiple fields from an object wrapped in a Java Optional
这个问题类似于 Reading multiple variables from an object wrapped in Option[] 但在 Java 而不是 Scala 的上下文中。
假设我有一个 return 是 Optional<Address>
的方法。
我需要从包装地址中提取多个字段,例如 getCity()
、getCountry()
和 getZIP()
。如果地址不存在,则应使用一些默认值。然后逻辑应该继续结果值。
问题是这样做的最好和最惯用的方法是什么。我能想到以下三个:
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
优点:
- 没有创建额外的对象 - 最有效的变体。
- 默认案例组合在一起。
缺点:
- 冗长。
2) 多链
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
请注意,这里的逻辑略有不同,因为默认值不仅在地址缺失时适用,而且在相应的地址字段为 null
时适用。
但这对我来说不是问题。
优点:
- 简洁。
- 变量立即初始化为一个值。
- 更接近于单个字段的完成方式。
缺点:
- 默认值分散开来。
- 对
map
的链接调用可能会创建中间对象(但以惯用的方式)。
- 也许在单个
Optional
上不合常理地使用多个链?
3a) 在对象中包装默认值
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
优点:
- 清晰度。
缺点:
- 字段被打包到一个
Address
中只是为了之后立即提取它们。
3b) 将默认值包装在常量中
正如@HariMenon 和@flakes 所建议的,具有默认值的 Address
可以存储在一个常量中,并在每次调用时重复使用以减少开销。
也可以懒惰地初始化 'constant':
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
加分题:
假设我完全控制了 getAddress()
方法,并且我知道所有用法都与此示例类似,但默认值不同。
我是否应该将其更改为 return null
而不是 Optional
以更好地适应 if-else
解决方案?
这有点主观,所以不会有正确答案。但这是我的 2 美分以及我这么认为的原因。选项 1 绝对不是 Java 8 的惯用方式。
在 2 和 3 之间,您会选择什么取决于您是要使用默认地址,还是要单独的默认城市、国家和邮政编码。如果 Address 不为 null,但 city 为 null,是否仍要使用默认城市?选项 3 会使它变得丑陋。否则,我更喜欢选项3。即使你把它包装成一个对象,那个对象也可以是一个名为DEFAULT_ADDRESS的常量,然后它到底是什么就很清楚了,因此更具可读性。
选项 2 使其看起来好像您有一个单独的默认国家/地区、城市和邮政编码,但似乎并非如此。但就像我说的,如果是这样,你应该使用它。
编辑 3b 和奖金:
如果您正在考虑包装器的性能影响,那您就想多了。在 Java 中创建这样的包装器非常便宜,现代 JIT 编译器在大多数情况下都会很好地优化它。除非通过测量证明代码是 perf 瓶颈,否则 perf 的可读性。对于 Optional 与 null,只需选择一种编码风格并在项目中坚持使用它。
如果您正在 returning Optionals,那么请确保该方法永远不会 return 为 null(一种可以 return Optional 和 null 的方法是可憎的)。许多人认为您应该始终使用 Optional
s。就个人而言,我有点不同意。如果它是一个新的代码库并且我们到处都在使用 Optionals,那就太好了。坚持下去,不要在任何地方使用 null 和 null 检查。如果它是一个已经在多个地方使用 null 的旧代码库,只需使用 null 并记录该方法在某些情况下可以 return null 。组合 Optional 和 null 会造成一团糟,我认为 Optional 提供的好处不足以抵消成本。可选项在从一开始就支持它们的语言中非常有用。但是它们是在 Java 中事后添加的,并且 Java 仍然有空值,所以 IMO 的好处实际上并不是那么好。不过这又是非常主观的,我会根据具体情况使用什么。
我同意@HariMenon 的观点。如果我是你,我会选择选项三。但是,不是每次都创建一个新的 Address
,而是只创建一次值并将地址存储在私有字段中。如果不存在合适的 ctor,您可以使用双括号初始化来稍微简化构造。只要确保不改变默认的 Address
变量即可。
private static final Address DEFAULT_ADDRESS = new Address() {{
setCity(null);
setCountry(Country.getUSA());
setZIP(ZIP.DEFAULT);
}};
...
Address addr = getAddress().orElse(DEFAULT_ADDRESS);
这个问题类似于 Reading multiple variables from an object wrapped in Option[] 但在 Java 而不是 Scala 的上下文中。
假设我有一个 return 是 Optional<Address>
的方法。
我需要从包装地址中提取多个字段,例如 getCity()
、getCountry()
和 getZIP()
。如果地址不存在,则应使用一些默认值。然后逻辑应该继续结果值。
问题是这样做的最好和最惯用的方法是什么。我能想到以下三个:
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
优点:
- 没有创建额外的对象 - 最有效的变体。
- 默认案例组合在一起。
缺点:
- 冗长。
2) 多链
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
请注意,这里的逻辑略有不同,因为默认值不仅在地址缺失时适用,而且在相应的地址字段为 null
时适用。
但这对我来说不是问题。
优点:
- 简洁。
- 变量立即初始化为一个值。
- 更接近于单个字段的完成方式。
缺点:
- 默认值分散开来。
- 对
map
的链接调用可能会创建中间对象(但以惯用的方式)。 - 也许在单个
Optional
上不合常理地使用多个链?
3a) 在对象中包装默认值
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
优点:
- 清晰度。
缺点:
- 字段被打包到一个
Address
中只是为了之后立即提取它们。
3b) 将默认值包装在常量中
正如@HariMenon 和@flakes 所建议的,具有默认值的 Address
可以存储在一个常量中,并在每次调用时重复使用以减少开销。
也可以懒惰地初始化 'constant':
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
加分题:
假设我完全控制了 getAddress()
方法,并且我知道所有用法都与此示例类似,但默认值不同。
我是否应该将其更改为 return null
而不是 Optional
以更好地适应 if-else
解决方案?
这有点主观,所以不会有正确答案。但这是我的 2 美分以及我这么认为的原因。选项 1 绝对不是 Java 8 的惯用方式。
在 2 和 3 之间,您会选择什么取决于您是要使用默认地址,还是要单独的默认城市、国家和邮政编码。如果 Address 不为 null,但 city 为 null,是否仍要使用默认城市?选项 3 会使它变得丑陋。否则,我更喜欢选项3。即使你把它包装成一个对象,那个对象也可以是一个名为DEFAULT_ADDRESS的常量,然后它到底是什么就很清楚了,因此更具可读性。
选项 2 使其看起来好像您有一个单独的默认国家/地区、城市和邮政编码,但似乎并非如此。但就像我说的,如果是这样,你应该使用它。
编辑 3b 和奖金: 如果您正在考虑包装器的性能影响,那您就想多了。在 Java 中创建这样的包装器非常便宜,现代 JIT 编译器在大多数情况下都会很好地优化它。除非通过测量证明代码是 perf 瓶颈,否则 perf 的可读性。对于 Optional 与 null,只需选择一种编码风格并在项目中坚持使用它。
如果您正在 returning Optionals,那么请确保该方法永远不会 return 为 null(一种可以 return Optional 和 null 的方法是可憎的)。许多人认为您应该始终使用 Optional
s。就个人而言,我有点不同意。如果它是一个新的代码库并且我们到处都在使用 Optionals,那就太好了。坚持下去,不要在任何地方使用 null 和 null 检查。如果它是一个已经在多个地方使用 null 的旧代码库,只需使用 null 并记录该方法在某些情况下可以 return null 。组合 Optional 和 null 会造成一团糟,我认为 Optional 提供的好处不足以抵消成本。可选项在从一开始就支持它们的语言中非常有用。但是它们是在 Java 中事后添加的,并且 Java 仍然有空值,所以 IMO 的好处实际上并不是那么好。不过这又是非常主观的,我会根据具体情况使用什么。
我同意@HariMenon 的观点。如果我是你,我会选择选项三。但是,不是每次都创建一个新的 Address
,而是只创建一次值并将地址存储在私有字段中。如果不存在合适的 ctor,您可以使用双括号初始化来稍微简化构造。只要确保不改变默认的 Address
变量即可。
private static final Address DEFAULT_ADDRESS = new Address() {{
setCity(null);
setCountry(Country.getUSA());
setZIP(ZIP.DEFAULT);
}};
...
Address addr = getAddress().orElse(DEFAULT_ADDRESS);