从包裹在 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 时适用。

但这对我来说不是问题。

优点:

缺点:

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

优点:

缺点:

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 的方法是可憎的)。许多人认为您应该始终使用 Optionals。就个人而言,我有点不同意。如果它是一个新的代码库并且我们到处都在使用 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);