【问题标题】:How to avoid checking for null values in method chaining? [duplicate]如何避免在方法链接中检查空值? [复制]
【发布时间】:2018-08-29 00:31:13
【问题描述】:

我需要检查某个值是否为空。如果它不为空,那么只需将一些变量设置为真。这里没有其他声明。我有太多这样的条件检查。

有什么方法可以在不检查所有方法返回值的情况下处理这种空检查?

if(country != null && country.getCity() != null && country.getCity().getSchool() != null && country.getCity().getSchool().getStudent() != null .....) {
    isValid = true;
}

我想过直接检查变量并忽略NullpointerException。这是一个好习惯吗?

try{
    if(country.getCity().getSchool().getStudent().getInfo().... != null)
} catch(NullPointerException ex){
    //dont do anything.
}

【问题讨论】:

  • 从 Java 8 开始有可选的
  • 好吧,您最终可以在getCity() 上获得null 值。这会导致 NPE,您无法确定 null 是什么
  • 作为一个信息点:在创建这些类型的链之前,您应该考虑Law of Demeter
  • 这段代码是一个巨大的危险信号:如果你发现自己在编写如此深度嵌套的链,请停止、重新考虑并重写你的代码。
  • 最好的解决方案是消除问题:实现这些方法,使它们永远不会返回 null。如果他们从不返回 null 那么您不必检查它。为什么 null 首先是合法的返回值?

标签: java exception nullpointerexception null


【解决方案1】:

不,在 Java 中捕获 NPE 而不是对引用进行空值检查通常不是一个好习惯。

如果您愿意,可以使用Optional 来处理这种事情:

if (Optional.ofNullable(country)
            .map(Country::getCity)
            .map(City::getSchool)
            .map(School::getStudent)
            .isPresent()) {
    isValid = true;
}

或者干脆

boolean isValid = Optional.ofNullable(country)
                          .map(Country::getCity)
                          .map(City::getSchool)
                          .map(School::getStudent)
                          .isPresent();

如果这就是 isValid 应该检查的全部内容。

【讨论】:

  • 请注意,只有在调用您的代码之前isValidfalse 时,您的两个示例才等效!
  • “不,在 Java 中允许 NPE 发生并不是一个好习惯。”我不同意。如果传入的数据无效,应该抛出异常。如果无效是不应该为 null 的内容为 null,则 NPE 是合适的。
  • 这段代码创建了多少临时对象?它比冗长的手动检查慢多少?
  • @pts "这段代码创建了多少临时对象?" 4个临时对象。 “慢了多少?”不太可能在实际程序中产生显着差异。
  • @pts:这段代码也非常适合内联和转义分析,所以如果这成为热点,JIT 很有可能无论如何都能够忽略分配这些临时对象。
【解决方案2】:

作为Optional 的其他精细用法的替代方法,我们还可以使用以Supplier<Object> var-args 作为参数的实用方法。
这是有道理的,因为我们在对象中没有很多嵌套级别要检查,但有很多字段要检查。
此外,当检测到null 时,可以轻松修改它以记录/处理某些内容。

    boolean isValid = isValid(() -> address, // first level
                              () -> address.getCity(),   // second level
                              () -> address.getCountry(),// second level
                              () -> address.getStreet(), // second level
                              () -> address.getZip(),    // second level
                              () -> address.getCountry() // third level
                                           .getISO()


@SafeVarargs
public static boolean isValid(Supplier<Object>... suppliers) {
    for (Supplier<Object> supplier : suppliers) {
        if (Objects.isNull(supplier.get())) {
            // log, handle specific thing if required
            return false;
        }
    }
    return true;
}

假设你想添加一些痕迹,你可以这样写:

boolean isValid = isValid(  Arrays.asList("address", "city", "country",
                                          "street", "zip", "Country ISO"),
                            () -> address, // first level
                            () -> address.getCity(),   // second level
                            () -> address.getCountry(),// second level
                            () -> address.getStreet(), // second level
                            () -> address.getZip(),    // second level
                            () -> address.getCountry() // third level
                                         .getISO()
                         );


@SafeVarargs
public static boolean isValid(List<String> fieldNames, Supplier<Object>... suppliers) {
    if (fieldNames.size() != suppliers.length){
         throw new IllegalArgumentException("...");
    }
    for (int i = 0; i < suppliers.length; i++) {
        if (Objects.isNull(suppliers.get(i).get())) {
            LOGGER.info( fieldNames.get(i) + " is null");
            return false;
        }
    }
    return true;
}

【讨论】:

  • 这甚至行不通 - 你应该链接函数(即使用前一个函数的返回值调用),而不仅仅是按顺序调用一些函数。
  • 我不这么认为。 address.getCountry,...,()-&gt; address.getCountry().getISO())等连锁供应商先测试getCountry(),如果不是null则测试返回国家的getISO()方法。
  • 调用a.b() 然后a.b().c()X tmp = a.b(); 后跟tmp.c() 不同。如果b() 每次调用都返回不同的值怎么办?仅仅因为它被称为getXXX 并不意味着它不会有副作用!
  • 多次调用具有副作用的吸气剂是一种非常糟糕的做法,请仔细阅读操作问题:“有没有办法在不检查所有方法返回值的情况下处理这种空检查?” .我的回答解决了这个问题。
【解决方案3】:

您还可以查看 vavr 的 Option,正如下面的帖子所述,它比 Java 的 Optional 更好,并且具有更丰富的 API。

https://softwaremill.com/do-we-have-better-option-here/

【讨论】:

  • 在 khelwood 接受的答案中描述的方法链接方面,vavr Option 类不会像 java Optional 类那样顺利。使用 vavr,一旦第一个 Option 测试不为空,您就会收到 Some 实例,而不是 None 实例。任何对 Some::map 的后续调用都将返回另一个 Some 实例,即使 map 调用返回了一个空值。如果您链接另一个地图调用,这可能会导致空指针。要使用 Option 进行菊花链,您需要在链中穿插非空检查过滤器 (filter(Optional::isPresent))。github.com/vavr-io/vavr/issues/2397
【解决方案4】:

面向对象的方法是将isValid方法放在Country和其他类中。它并没有减少空检查的数量,但每个方法只有一个,你不要重复它们。

public boolean isValid() {
  return city != null && city.isValid();
}

这假设验证在使用您的国家/地区的任何地方都是相同的,但通常情况就是这样。如果不是,则该方法应命名为 hasStudent(),但这不太通用,并且您冒着在 Country 中复制整个 School 接口的风险。例如,在另一个地方你可能需要 hasTeacher() 或 hasCourse()。

另一种方法是使用空对象:

public class Country {
  public static final Country NO_COUNTRY = new Country();

  private City city = City.NO_CITY;

  // etc.
}

我不确定这种情况是否更可取(严格来说,您需要一个子类来覆盖所有修改方法),Java 8 的方法是在其他答案中使用 Optional 作为方法,但我建议更全面地接受它:

private Optional<City> city = Optional.ofNullable(city);

public Optional<City> getCity() {
   return city;
}

对于 null 对象和 Nullable 只有在您始终使用它们而不是 null 时才有效(注意字段初始化),否则您仍然需要 null 检查。所以这个选项避免了 null,但是你的代码变得更加冗长,以减少其他地方的 null 检查。

当然,正确的设计可能是尽可能使用集合(而不是可选)。一个国家有一组城市,城市有一组学校,学校有一组学生,等等。

【讨论】:

    【解决方案5】:

    Java 没有“空安全”操作,例如 Kotlin's null safety

    您可以:

    • 捕获 NPE 并忽略它
    • 手动检查所有参考文献
    • 根据其他答案使用 Optional
    • 使用某种工具,例如 XLST

    否则,如果您可以控制域对象,则可以重新设计您的类,以便从顶级对象中获得您需要的信息(让 Country 类执行所有空值检查...)

    【讨论】:

      【解决方案6】:

      您可以在这里使用Optional,但它会在每一步创建一个可选对象。

      boolean isValid = Optional.ofNullable(country)
          .map(country -> country.getCity()) //Or use method reference Country::getCity
          .map(city -> city.getSchool())
          .map(school -> school.getStudent())
          .map(student -> true)
          .orElse(false);
      
      //OR
      boolean isValid = Optional.ofNullable(country)
                            .map(..)
                            ....
                            .isPresent();
      

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-01
      • 2013-07-01
      • 2023-01-11
      • 2022-12-06
      • 2011-02-14
      相关资源
      最近更新 更多