如何避免在方法链中检查空值?

44 java exception-handling nullpointerexception

我需要检查一些值是否为null.如果它不为null,那么只需将一些变量设置为true即可.这里没有其他声明.我得到了太多像这样的条件检查.

有没有办法处理这个空检查而不检查所有方法返回值?

if(country != null && country.getCity() != null && country.getCity().getSchool() != null && country.getCity().getSchool().getStudent() != null .....) {
    isValid = true;
}
Run Code Online (Sandbox Code Playgroud)

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

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

khe*_*ood 67

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

Optional如果你愿意,你可以使用这种东西:

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

或者干脆

boolean isValid = Optional.ofNullable(country)
                          .map(Country::getCity)
                          .map(City::getSchool)
                          .map(School::getStudent)
                          .isPresent();
Run Code Online (Sandbox Code Playgroud)

如果那是isValid应该检查的全部.

  • 第二个代码示例中的括号是不必要的. (11认同)
  • 请注意,在调用代码之前,如果`isValid`为`false`,那么您的两个示例只是等效的! (10认同)
  • @khelwood确实如此主观,对我来说它实际上会恶化可读性. (7认同)
  • @GiacomoAlzetta有时括号可以使代码更容易让读者遵循,但这是一种主观判断. (3认同)
  • @pts:这段代码也非常适合内联和转义分析,所以如果这成为一个热点,那么JIT很有可能无论如何都无法分配那些临时对象. (3认同)

use*_*er7 16

您可以Optional在这里使用,但它会在每一步创建一个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();
Run Code Online (Sandbox Code Playgroud)

  • @TejasKale看看https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html (4认同)

Wal*_*aan 7

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

public boolean isValid() {
  return city != null && city.isValid();
}
Run Code Online (Sandbox Code Playgroud)

这假设在您的国家/地区使用的地方验证是相同的,但通常就是这种情况.如果没有,该方法应命名为hasStudent(),但这不太通用,您可能会在Country中复制整个School界面.例如,在另一个地方,您可能需要hasTeacher()或hasCourse().

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

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

  private City city = City.NO_CITY;

  // etc.
}
Run Code Online (Sandbox Code Playgroud)

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

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

public Optional<City> getCity() {
   return city;
}
Run Code Online (Sandbox Code Playgroud)

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

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


dav*_*xxx 5

作为其他精细用法的替代方法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;
}
Run Code Online (Sandbox Code Playgroud)

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

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;
}
Run Code Online (Sandbox Code Playgroud)