从包装在Java Optional

时间:2019-03-02 07:12:19

标签: java optional

这个问题类似于Reading multiple variables from an object wrapped in Option[],但在Java而不是Scala的上下文中。

假设我有一个返回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可以存储在常量中,并可以在每次调用中重复使用,以减少开销。

一个人也可以懒惰地初始化“常量”:

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()方法,并且我知道所有用法都与此示例相似,但是默认值有所不同。 我应该更改它以返回null而不是Optional来更好地适应if-else解决方案吗?

2 个答案:

答案 0 :(得分:1)

这有点主观,所以不会有正确的答案。但是,这是我的2美分,为什么我这么认为。选项1绝对不是Java 8惯用的方式。

在2和3之间,您选择什么取决于您是要使用默认地址,还是要使用单独的默认城市,国家和邮政编码。如果“地址”不为空,但城市为空,您是否仍要使用默认城市?选项3会让事情变得很丑陋。否则,我会选择选项3。即使将其包装到一个对象中,该对象也可以是名为DEFAULT_ADDRESS的常量,然后可以很清楚地知道它是什么,因此可读性更高。

选项2使其看起来好像您有一个单独的默认国家/地区,城市和邮政编码,但实际情况并非如此。但是就像我说的,如果是这样,您应该使用它。

编辑3b和奖励: 如果您正在考虑包装的性能影响,那么您会认为它太过思考了。在Java中创建这样的包装器非常便宜,现代的JIT编译器在大多数情况下都能很好地优化此包装器。性能上的可读性,除非经过测量证明该代码是性能瓶颈。对于Optional vs null,只需选择一种编码样式并坚持使用该项目即可。

如果返回的是Optional,请确保该方法从不返回null(可同时返回Optional和null的方法是可憎的)。许多人认为您应该始终使用Optional。就个人而言,我略有不同意。如果这是一个新的代码库,并且我们在各处都使用Optionals,那么它很棒。坚持使用它,不要在任何地方使用null和null检查。如果它是旧的代码库,并且已经在多个地方使用了null,则只需使用null,并记录该方法在某些情况下可以返回null。结合使用Optional和null会造成可怕的混乱,我认为Optional并不能提供足够的好处来抵消这一成本。从一开始就支持可选项的语言就很棒。但是它们是Java中的事后添加的,而Java仍然具有null,因此IMO的好处实际上并不是那么大。不过,这又是非常主观的,我将使用的内容会因情况而异。

答案 1 :(得分:0)

我同意@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);