关于java:如何避免在方法链中检查空值?

How to avoid checking for null values in method chaining?

本问题已经有最佳答案,请猛点这里访问。

我需要检查一些值是否为空。如果它不是空的,那么只需将某个变量设置为真。这里没有别的说法。我有太多这样的状况检查。

是否有任何方法可以在不检查所有方法返回值的情况下处理此空检查?

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

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

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


不,在Java中捕获NPE通常不是很好的做法,而不是NULL检查引用。

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

1
2
3
4
5
6
7
if (Optional.ofNullable(country)
            .map(Country::getCity)
            .map(City::getSchool)
            .map(School::getStudent)
            .isPresent()) {
    isValid = true;
}

或者简单地说

1
2
3
4
5
boolean isValid = Optional.ofNullable(country)
                          .map(Country::getCity)
                          .map(City::getSchool)
                          .map(School::getStudent)
                          .isPresent();

如果这就是EDOCX1[1]应该检查的全部内容。


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

1
2
3
4
5
6
7
8
9
10
11
12
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();


面向对象的方法是将isvalid方法应用到国家和其他类中。它不会减少空检查的数量,但是每个方法只有一个空检查,并且您不会重复它们。

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

这有一个假设,即在您的国家使用的任何地方,验证都是相同的,但通常情况是这样的。如果不是这样,该方法应该命名为hasstudent(),但这不太常见,您可能会面临在国内复制整个学校界面的风险。例如,在另一个地方,您可能需要hasteacher()或hasscourse()。

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

1
2
3
4
5
6
7
public class Country {
  public static final Country NO_COUNTRY = new Country();

  private City city = City.NO_CITY;

  // etc.
}

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

1
2
3
4
5
private Optional<City> city = Optional.ofNullable(city);

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

对于空对象和可以为空的对象,只有始终使用它们而不是空(注意字段初始化),否则仍然需要进行空检查。所以这个选项避免使用空值,但是您的代码会变得更冗长,从而减少了在其他地方的空值检查。

当然,正确的设计可能是在可能的情况下使用集合(而不是可选的)。一个国家有一套城市,城市有一套学校,其中有一套学生等。


除了其他精细使用Optional之外,我们还可以使用一个以Suppliervar参数为参数的实用方法。这是有意义的,因为我们在对象中没有许多嵌套的级别需要检查,但是有许多字段需要检查。此外,当检测到null时,可以很容易地修改它来记录/处理某些内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    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;
}

如果要添加一些跟踪,可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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;
}


Java没有"零安全"操作,例如Kotlin的零安全性。

您可以:

  • 抓住NPE并忽略它
  • 手动检查所有引用
  • 根据其他答案使用可选选项
  • 使用像XLST这样的工具

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


您还可以查看Vavr的选项,因为下面的帖子描述的比Java的可选的要好,而且API更丰富。

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