如何在Java中避免检查null值?

4393

我使用 x != null 来避免 NullPointerException。有其他的替代方法吗?

if (x != null) {
    // ...
}

142
鼓励使用空值会使代码更难理解,也更不可靠。 - Tom Hawtin - tackline
83
不使用null比这里大多数其他建议都更好。抛出异常,不要返回或允许null。顺便说一下,“assert”关键字是无用的,因为它默认情况下是禁用的。使用一个始终启用的失败机制。 - ianpojman
4
在高级代码中应避免使用null值。发明了null引用的Tony Hoare称其为“价值数十亿美元的错误”。在此处查看一些想法:http://www.softwaregeek.net/2016/04/how-to-avoid-null-checks-in-java.html。 - Andrii Polunin
3
似乎是Java 8中的静态方法:static Objects.isNull(Object o)。可在以下链接中找到具体文档:https://docs.oracle.com/javase/8/docs/api/java/util/Objects.html - Robert R Evans
7
null最常见的误用是人们返回null而不是空集合。每次这样做都会让我发疯。停止使用null,你将生活在一个更美好的世界中。此外,通过使用"final"关键字来扩展你的代码,你将生活在一个更美好的世界中。 - Jack
显示剩余8条评论
68个回答

32

Guava是谷歌提供的非常实用的核心库,它有一个漂亮且实用的API来避免null值。我发现UsingAndAvoidingNullExplained非常有帮助。

正如维基中所解释的:

Optional<T>是一种用非空值替换可为空的T引用的方式。Optional可能包含非空的T引用(这种情况下我们说引用“存在”),也可能不包含任何内容(这种情况下我们说引用“不存在”)。从不说它“包含null”。

用法:

Optional<Integer> possible = Optional.of(5);
possible.isPresent(); // returns true
possible.get(); // returns 5

@CodyGuldner 没错,Cody。我提供了一个相关的引用来给更多的背景。 - Murat Derya Özen
使用 get() 不是一个好主意。您可以在一行中使用以下语句:val = funcThatReturnsOptional().orElse(CUSTOM_ERROR_DIFFERENT_USECASES); 并检查不同用例的值。 - Tugalsan Karabacak

28
这是每个Java开发人员都面临的一个非常普遍的问题。因此,Java 8提供了官方支持,以解决这些问题,而不会使代码混乱。
Java 8引入了java.util.Optional<T>。它是一个容器,可能包含或不包含非空值。Java 8为处理对象在某些情况下可能为空的更安全方式提供了一种方法。它受到HaskellScala的启发。
简而言之,Optional类包括用于明确处理存在或不存在值的情况的方法。但是,与null引用相比的优点是,Optional<T>类强制您考虑值不存在的情况。因此,可以防止意外的空指针异常。
在上面的例子中,我们有一个家庭服务工厂,返回一个句柄,该句柄指向家庭中可用的多个电器。但是这些服务可能可用/不可用;这意味着可能会导致NullPointerException。让我们将其包装到Optional<Service>中,而不是在使用任何服务之前添加null if条件。
将其包装到Optional<T>中
让我们考虑一个从工厂获取服务引用的方法。将其与Optional一起包装,而不是返回服务引用。它让API用户知道返回的服务可能可用/不可用,使用防御性。
public Optional<Service> getRefrigertorControl() {
      Service s = new  RefrigeratorService();
       //...
      return Optional.ofNullable(s);
   }

作为你所看到的,Optional.ofNullable()提供了一种轻松获取包装引用的方式。还有另外两种获取Optional引用的方式,分别是Optional.empty()Optional.of()。前者返回一个空对象而不是null,后者用于包装非空对象。
那么它到底如何帮助避免空指针检查呢?
一旦你包装了一个引用对象,Optional提供了许多有用的方法来在不出现NPE的情况下调用包装引用上的方法。
Optional ref = homeServices.getRefrigertorControl();
ref.ifPresent(HomeServices::switchItOn);

Optional.ifPresent会在参考值非空时使用给定的Consumer。否则,它将不执行任何操作。

@FunctionalInterface
public interface Consumer<T>

代表接受单个输入参数并不返回结果的操作。与大多数其他功能接口不同,Consumer 预计通过副作用进行操作。 它非常干净易懂。在上面的代码示例中,如果 Optional 持有引用非空,则会调用 HomeService.switchOn(Service)。 我们经常使用三元运算符来检查 null 条件并返回替代值或默认值。Optional 提供了另一种处理相同条件而无需检查 null 的方法。Optional.orElse(defaultObj) 如果 Optional 为 null,则返回 defaultObj。让我们在我们的示例代码中使用它:
public static Optional<HomeServices> get() {
    service = Optional.of(service.orElse(new HomeServices()));
    return service;
}

现在,HomeServices.get()以更好的方式执行相同的操作。它会检查服务是否已经初始化。如果是,则返回相同的服务或创建新服务。Optional<T>.orElse(T)可帮助返回默认值。
最后,这是我们没有NPE和null检查的代码:
import java.util.Optional;
public class HomeServices {
    private static final int NOW = 0;
    private static Optional<HomeServices> service;

public static Optional<HomeServices> get() {
    service = Optional.of(service.orElse(new HomeServices()));
    return service;
}

public Optional<Service> getRefrigertorControl() {
    Service s = new  RefrigeratorService();
    //...
    return Optional.ofNullable(s);
}

public static void main(String[] args) {
    /* Get Home Services handle */
    Optional<HomeServices> homeServices = HomeServices.get();
    if(homeServices != null) {
        Optional<Service> refrigertorControl = homeServices.get().getRefrigertorControl();
        refrigertorControl.ifPresent(HomeServices::switchItOn);
    }
}

public static void switchItOn(Service s){
         //...
    }
}

完整的文章是NPE以及无需空值检查的代码……真的吗?


上面的代码中有一个空值检查 - if(homeServices != null) {,可以改为 homeServices.ifPresent(h -> //action); - KrishPrabakar

25

我喜欢Nat Pryce的文章。以下是链接:

文章中还有一个链接到Java Maybe类型的Git存储库,我觉得很有趣,但我认为它本身不能降低检查代码膨胀。在互联网上进行了一些研究后,我认为通过谨慎设计可以主要减少!= null代码膨胀。


Michael Feathers写了一篇简短而有趣的文章,讨论了像你提到的方法:http://manuelp.newsblur.com/site/424/ - ivan.aguirre
如果有什么答案让我最欣赏的,那就是“精心设计”的提示。无论是编写代码、修复错误还是检查空值,都非常重要的是在代码中进行整体良好的设计和组织,以避免因为很久以前做出的糟糕设计选择而导致今天仍然需要处理许多冗余。 - Skaldebane

23

我尝试了NullObjectPattern,但对我来说并不总是最好的选择。有时候“无操作”是不合适的。

NullPointerException是一个运行时异常,这意味着它是开发人员的错,并且通过足够的经验可以告诉你错误出在哪里。

现在来到答案:

尽可能将所有属性及其访问器设置为私有或完全避免将它们暴露给客户端。当然,您可以在构造函数中拥有参数值,但通过减少作用域,您不会让客户类传递无效值。如果需要修改值,则可以创建一个新的object。您仅在构造函数中检查值一次,在其他方法中,几乎可以确定值不为空。

当然,要理解和应用此建议,经验是更好的方式。

Byte!


22

对于Java 8或更新版本,最好的替代方案可能是使用Optional类。

Optional stringToUse = Optional.of("optional is there");
stringToUse.ifPresent(System.out::println);

这对于可能出现长链式的空值特别方便。例如:

Optional<Integer> i = Optional.ofNullable(wsObject.getFoo())
    .map(f -> f.getBar())
    .map(b -> b.getBaz())
    .map(b -> b.getInt());

在空值时抛出异常的示例:
Optional optionalCarNull = Optional.ofNullable(someNull);
optionalCarNull.orElseThrow(IllegalStateException::new);

Java 7引入了Objects.requireNonNull方法,当需要检查非空性时,它会很方便。例如:
String lowerVal = Objects.requireNonNull(someVar, "input cannot be null or empty").toLowerCase();

你说,“这对于可能存在长链的空值特别方便”。能否请您解释一下? - likejudo

18
我可以更加通俗易懂地回答这个问题!在编程中,当方法获取的参数与我们预期的方式不同(糟糕的方法调用是程序员的错误)时,我们通常会面临这个问题。例如:你期望得到一个对象,但实际上你得到了null;你期望得到至少一个字符的字符串,但实际上你得到了一个空字符串... 所以,在这种情况下,没有任何区别:
if(object == null){
   //you called my method badly!

}

或者

if(str.length() == 0){
   //you called my method badly again!
}

他们都希望确保我们在执行其他函数之前接收到了有效的参数。
正如其他答案中提到的那样,为了避免上述问题,您可以遵循“契约设计”模式。请参见http://en.wikipedia.org/wiki/Design_by_contract
要在Java中实现此模式,您可以使用核心Java注释,例如javax.annotation.NotNull或使用更复杂的库,例如Hibernate Validator
仅供参考:
getCustomerAccounts(@NotEmpty String customerId,@Size(min = 1) String accountType)

现在,您可以在不需要检查输入参数的情况下安全地开发方法的核心功能,它们可以保护您的方法免受意外的参数影响。
您可以进一步确保在您的应用程序中只能创建有效的pojo。(摘自hibernate验证器网站的示例)
public class Car {

   @NotNull
   private String manufacturer;

   @NotNull
   @Size(min = 2, max = 14)
   private String licensePlate;

   @Min(2)
   private int seatCount;

   // ...
}

按照定义,javax 不是“核心Java”。 - Tomas

17

我强烈反对建议在任何情况下都使用null对象。这种模式可能会破坏协议并将问题深埋,而不是解决它们,更不用说不当使用会创建另一堆需要未来维护的样板代码。

实际上,如果从方法返回的内容可能为null并且调用代码必须对其进行决策,则应该有一个早期调用来确保状态。

此外要记住,如果不小心使用null对象模式会消耗大量内存。因此,NullObject的实例应该在所有者之间共享,而不是每个所有者拥有一个唯一实例。

此外,我不建议在类型旨在表示原始类型 - 如数学实体,这些实体不是标量:向量、矩阵、复数和POD(Plain Old Data)对象,这些对象旨在以Java内置类型的形式保存状态时使用此模式。在后一种情况下,您将最终调用具有任意结果的getter方法。例如,NullPerson.getName()方法应该返回什么?

值得考虑这种情况以避免荒谬的结果。


具有“hasBackground()”的解决方案有一个缺点 - 它不是线程安全的。如果你需要调用两个方法而不是一个,你需要在多线程环境中同步整个序列。 - pkalinow
@pkalinow,你只是举了一个人为的例子来指出这种解决方案的缺点。如果代码不打算在多线程应用程序中运行,那么就没有缺点。我可以告诉你,你的代码中可能有90%都不是线程安全的。我们在这里讨论的不是代码的这个方面,而是设计模式。而多线程是一个独立的主题。 - luke1985
当然,在单线程应用程序中这不是问题。我发表这个评论是因为有时候它确实是一个问题。 - pkalinow
如果你更深入地研究这个主题,你会发现 Null Object 设计模式无法解决多线程问题。所以它是不相关的。 而且说实话,我已经找到了一些适合使用这种模式的地方,所以我的原始答案有点错误。 - luke1985

16
  1. 永远不要将变量初始化为null。
  2. 如果(1)不可行,请将所有集合和数组初始化为空集合/数组。

在自己的代码中这样做,就可以避免!=null的检查。

大多数情况下,null检查似乎是为了保护对集合或数组的循环,因此只需将它们初始化为空,您将不需要任何null检查。

// Bad
ArrayList<String> lemmings;
String[] names;

void checkLemmings() {
    if (lemmings != null) for(lemming: lemmings) {
        // do something
    }
}



// Good
ArrayList<String> lemmings = new ArrayList<String>();
String[] names = {};

void checkLemmings() {
    for(lemming: lemmings) {
        // do something
    }
}

虽然有一点点额外的开销,但为了更干净的代码和更少的NullPointerExceptions,这是值得的。


https://dev59.com/WnM_5IYBdhLWcg3waieJ - Terence
3
我同意这一点。你绝不能返回初始化不完整的对象。Jaxb相关代码和bean代码因此而臭名昭著。这是糟糕的做法。所有集合都应该被初始化,并且所有对象应该存在(最好)没有空引用。考虑一个包含集合的对象。检查对象不为空,集合不为空以及集合不包含空对象是不合理和愚蠢的。 - ggb667

15
这是大多数开发者遇到的最常见错误。
我们有很多方法来处理它。
方法1:
org.apache.commons.lang.Validate //using apache framework

notNull(Object object, String message)

方法2:

if(someObject!=null){ // simply checking against null
}

方法三:

@isNull @Nullable  // using annotation based validation

方法四:

// by writing static method and calling it across whereever we needed to check the validation

static <T> T isNull(someObject e){  
   if(e == null){
      throw new NullPointerException();
   }
   return e;
}

广告4. 这并不是很有用 - 当您检查指针是否为空时,您可能想要在其上调用一个方法。在空指针上调用方法会给您相同的行为 - NullPointerException。 - pkalinow
第四种方法是多余的,因为Objects.requireNonNull()已经涵盖了它。 - fozzybear

12

总之,为了避免声明。

if (object != null) {
    ....
}
  1. 自Java 7起,您可以使用Objects方法:

    Objects.isNull(object)

    Objects.nonNull(object)

    Objects.requireNonNull(object)

    Objects.equals(object1, object2)

  2. 自Java 8起,您可以使用Optional类(何时使用

object.ifPresent(obj -> ...); Java 8

object.ifPresentOrElse(obj -> ..., () -> ...); Java 9

  1. 依赖于方法契约(JSR 305),并使用Find Bugs。使用注释@javax.annotation.Nullable@javax.annotation.Nonnnul标记代码。同时可用Preconditions。

    Preconditions.checkNotNull(object);

  2. 在特殊情况下(例如对于字符串和集合),您可以使用apache-commons(或Google guava)的实用程序方法:

public static boolean isEmpty(CharSequence cs) //apache CollectionUtils

public static boolean isEmpty(Collection coll) //apache StringUtils

public static boolean isEmpty(Map map) //apache MapUtils

public static boolean isNullOrEmpty(@Nullable String string) //Guava Strings

  1. 当您需要在null时分配默认值时,请使用apache commons lang

public static Object defaultIfNull(Object object, Object defaultValue)


解释得非常好 - Brijesh Mavani

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接