在 Java 中避免 NullPointerException

Posted

技术标签:

【中文标题】在 Java 中避免 NullPointerException【英文标题】:Avoiding NullPointerException in Java 【发布时间】:2010-09-21 06:06:30 【问题描述】:

我经常使用object != null 来避免NullPointerException

什么是替代品:

if (someobject != null) 
    someobject.doCalc();

【问题讨论】:

@Shervin 鼓励使用空值会使代码难以理解且可靠性降低。 不使用 null 优于此处的大多数其他建议。抛出异常,不返回或允许空值。顺便说一句 - 'assert' 关键字是无用的,因为默认情况下它是禁用的。使用始终启用的故障机制 难道没有设计模式谈到制作空对象吗?因此,当实例化一个新对象时,您总是使用这个空对象(相同的超类,相同的 [但为空] 方法),然后在需要时将该对象设置为完整对象。您可能想查看类似的问题:***.com/questions/9162371/… 以及有关空对象模式的信息:en.wikipedia.org/wiki/Null_Object_pattern 在高级代码中应该避免空值。发明空引用的托尼·霍尔(Tony Hoare)称它们为“十亿美元的错误”。看看here 了解一些想法。 似乎在 Java 8 中:static Objects.isNull(Object o) docs.oracle.com/javase/8/docs/api/java/util/Objects.html 【参考方案1】:

您可能会考虑空对象是错误的情况,而不是空对象模式(它有其用途)。

当抛出异常时,检查堆栈跟踪并解决错误。

【讨论】:

问题是通常您会丢失上下文,因为 NullPointerException 并不表示 WHICH 变量为空,并且您可能有多个“.”操作在线。使用 "if (foo == null) throw new RuntimeException("foo == null")" 可以让您明确指出什么是错误的,从而为那些必须修复它的人提供更多价值。 使用 Andersen - 我希望 Java 的异常系统能够包含正在处理的变量的名称,这样 NullPointerExceptions 不仅会指示发生异常的行,而且还会指示变量姓名。这在未混淆的软件中应该可以正常工作。 我有一位教授反对方法调用链。他的理论是,您应该警惕超过 2 种方法的调用链。我不知道这是否是一个硬性规则,但它确实消除了 NPE 堆栈跟踪的大部分问题。【参考方案2】: 如果您认为对象不应为 null(或者它是错误),请使用断言。 如果您的方法不接受空参数,请在 javadoc 中说明并使用断言。

只有当您想处理对象可能为 null 的情况时,您才需要检查 object != null...

有一个提议在 Java7 中添加新的注解来帮助使用 null / notnull 参数: http://tech.puredanger.com/java7/#jsr308

【讨论】:

不,do not use assertions in production code。【参考方案3】:

有时,您有对其参数进行操作的方法,这些参数定义了对称操作:

a.f(b); <-> b.f(a);

如果你知道 b 永远不能为空,你可以交换它。它对equals最有用: 而不是foo.equals("bar"); 更好"bar".equals(foo);

【讨论】:

但是你必须假设equals(可以是任何方法)将正确处理null。实际上,这一切都在将责任转嫁给其他人(或其他方法)。 @Supericy 基本上是的,但是equals(或任何方法)无论如何都必须检查null。或者明确声明它没有。【参考方案4】:

根据您检查的对象类型,您可以使用 apache commons 中的一些类,例如:apache commons lang 和 apache commons collections

例子:

String foo;
...
if( StringUtils.isBlank( foo ) ) 
   ///do something

或(取决于您需要检查的内容):

String foo;
...
if( StringUtils.isEmpty( foo ) ) 
   ///do something

StringUtils 类只是众多类之一;在公地中有很多很好的类可以进行 null 安全操作。

下面是一个示例,说明在包含 apache 库时如何在 JAVA 中使用 null vallidation(commons-lang-2.4.jar)

public DOCUMENT read(String xml, ValidationEventHandler validationEventHandler) 
    Validate.notNull(validationEventHandler,"ValidationHandler not Injected");
    return read(new StringReader(xml), true, validationEventHandler);

如果你使用的是 Spring,Spring 的包中也有相同的功能,请参见 library(spring-2.4.6.jar)

关于如何使用这个来自 spring(org.springframework.util.Assert) 的静态类的示例

Assert.notNull(validationEventHandler,"ValidationHandler not Injected");

【讨论】:

您也可以使用来自 Apache Commons 的更通用的版本,在我发现的检查参数的方法开始时非常有用。 Validate.notNull(object, "object 不能为 null"); commons.apache.org/lang/apidocs/org/apache/commons/lang/… @monojohnny 是否 Validate 使用 Assert statements into?。我这样问是因为 Assert 可能在 JVM 上被激活/停用,建议不要在生产中使用。 不要这么认为 - 我相信如果验证失败,它只会抛出 RuntimeException【参考方案5】:

如果不允许空值

如果您的方法被外部调用,请从以下内容开始:

public void method(Object object) 
  if (object == null) 
    throw new IllegalArgumentException("...");
  

然后,在该方法的其余部分中,您将知道object 不为空。

如果它是一个内部方法(不是 API 的一部分),只需记录它不能为 null,就是这样。

例子:

public String getFirst3Chars(String text) 
  return text.subString(0, 3);

但是,如果您的方法只是传递值,而下一个方法传递它等等,则可能会出现问题。在这种情况下,您可能需要检查上述参数。

如果允许为空

这真的取决于。如果发现我经常这样做:

if (object == null) 
  // something
 else 
  // something else

所以我分支,做两件完全不同的事情。没有丑陋的代码sn-p,因为我确实需要根据数据做两件不同的事情。例如,我应该处理输入,还是应该计算一个好的默认值?


我实际上很少使用成语“if (object != null &amp;&amp; ...”。

如果你举例说明你通常在哪里使用习语,给你举例可能会更容易。

【讨论】:

抛出 IllegalArgumentException 有什么意义?我认为 NullPointerException 会更清楚,如果您自己不进行 null 检查,也会抛出这种情况。我要么使用断言,要么什么都不用。 不太可能接受除 null 之外的所有其他值。你可能有 IllegalArgumentException、OutOfRageException 等。有时这是有道理的。有时您最终会创建许多没有任何价值的异常类,那么您只需使用 IllegalArgumentException。对空输入有一个例外,而对其他一切有另一个例外是没有意义的。 是的,我同意fail-fast-principle,但是在上面给出的示例中,值不是传递的,而是应该调用方法的对象。所以它失败的速度同样快,并且添加一个空检查只是为了抛出一个无论如何都会在同一时间和地点抛出的异常似乎并不会使调试变得更容易。 安全漏洞? JDK 充满了这样的代码。如果您不希望用户看到堆栈跟踪,则只需禁用它们。没有人暗示没有记录该行为。 mysql 是用 C 语言编写的,其中取消引用空指针是未定义的行为,与抛出异常无关。 throw new IllegalArgumentException("object==null")【参考方案6】:

问这个问题表明您可能对错误处理策略感兴趣。如何以及在何处处理错误是一个普遍存在的架构问题。有几种方法可以做到这一点。

我最喜欢的:允许异常泛滥——在“主循环”或其他具有适当职责的函数中捕获它们。检查错误情况并适当处理它们可以被视为一项特殊责任。

当然也可以看看面向方面的编程 - 他们有巧妙的方法将if( o == null ) handleNull() 插入到您的字节码中。

【讨论】:

【参考方案7】:

无论您在何处传递数组或向量,都将它们初始化为空,而不是 null。 - 这样你可以避免大量检查 null 并且一切都很好:)

public class NonNullThing 

   Vector vectorField = new Vector();

   int[] arrayField = new int[0];

   public NonNullThing() 

      // etc

   


【讨论】:

【参考方案8】:

在我看来,这听起来像是初级到中级开发人员在某些时候往往会面临的一个相当普遍的问题:他们要么不知道,要么不信任他们参与的合约,并且防御性地过度检查空值。此外,在编写自己的代码时,他们倾向于依靠返回空值来指示某些内容,因此需要调用者检查空值。

换句话说,有两种情况会出现空值检查:

    其中 null 是合同条款中的有效响应;和

    如果不是有效响应。

(2) 很简单。使用assert 语句(断言)或允许失败(例如,NullPointerException)。断言是在 1.4 中添加的一个未被充分利用的 Java 特性。语法是:

assert <condition>

assert <condition> : <object>

其中&lt;condition&gt; 是一个布尔表达式,&lt;object&gt; 是一个对象,其toString() 方法的输出将包含在错误中。

如果条件不成立,assert 语句将引发 Error (AssertionError)。默认情况下,Java 忽略断言。您可以通过将选项 -ea 传递给 JVM 来启用断言。您可以启用和禁用单个类和包的断言。这意味着您可以在开发和测试时使用断言验证代码,并在生产环境中禁用它们,尽管我的测试显示断言几乎不会影响性能。

在这种情况下不使用断言是可以的,因为代码只会失败,如果你使用断言就会发生这种情况。唯一的区别是,如果使用断言,它可能会以更有意义的方式更快地发生,并且可能带有额外的信息,这可能会帮助您弄清楚如果您没有预料到它为什么会发生。

(1) 有点难。如果您无法控制所调用的代码,那么您将陷入困境。如果 null 是一个有效的响应,你必须检查它。

但是,如果是您控制的代码(这种情况经常发生),那就另当别论了。避免使用空值作为响应。使用返回集合的方法,这很容易:几乎总是返回空集合(或数组)而不是 null。

使用非收藏品可能会更难。以此为例:如果您有这些接口:

public interface Action 
  void doSomething();


public interface Parser 
  Action findAction(String userInput);

Parser 获取原始用户输入并找到要做的事情,也许如果您正在为某事实现命令行界面。现在,如果没有适当的操作,您可能会制定返回 null 的合同。这会导致您正在谈论的空值检查。

另一种解决方案是永远不要返回 null,而是使用 Null Object pattern:

public class MyParser implements Parser 
  private static Action DO_NOTHING = new Action() 
    public void doSomething()  /* do nothing */ 
  ;

  public Action findAction(String userInput) 
    // ...
    if ( /* we can't find any actions */ ) 
      return DO_NOTHING;
    
  

比较:

Parser parser = ParserFactory.getParser();
if (parser == null) 
  // now what?
  // this would be an example of where null isn't (or shouldn't be) a valid response

Action action = parser.findAction(someInput);
if (action == null) 
  // do nothing
 else 
  action.doSomething();

ParserFactory.getParser().findAction(someInput).doSomething();

这是一个更好的设计,因为它导致代码更简洁。

也就是说,findAction() 方法抛出一个带有有意义的错误消息的异常可能是完全合适的——尤其是在这种依赖用户输入的情况下。 findAction 方法抛出 Exception 比调用方法抛出一个简单的 NullPointerException 而没有任何解释要好得多。

try 
    ParserFactory.getParser().findAction(someInput).doSomething();
 catch(ActionNotFoundException anfe) 
    userConsole.err(anfe.getMessage());

或者,如果您认为 try/catch 机制太丑陋,而不是什么都不做,您的默认操作应该向用户提供反馈。

public Action findAction(final String userInput) 
    /* Code to return requested Action if found */
    return new Action() 
        public void doSomething() 
            userConsole.err("Action not found: " + userInput);
        
    

【讨论】:

很好的答案,尤其是提到断言。我来自 C 语言并经常使用它们。至于空对象,它们不是灵丹妙药。我经常花费数小时调试代码,结果证明这是一个正在使用的 Null 对象,当 NPE 可以更清楚地指出问题时,什么都不做(或返回其默认值)。因此,您将不得不进行“空对象检查”而不是空检查,并且没有赢得任何东西,实际上失去了清晰度。现在,如果我不能避免 null,我只是清楚地记录它,就是这样。【参考方案9】:

仅适用于这种情况-

在调用 equals 方法之前不检查变量是否为空(下面的字符串比较示例):

if ( foo.equals("bar") ) 
 // ...

如果foo 不存在,将产生NullPointerException

如果你像这样比较你的Strings,你可以避免这种情况:

if ( "bar".equals(foo) ) 
 // ...

【讨论】:

我同意 - 只有在那种情况下。我不能忍受那些把它带到下一个不必要的水平并写 if (null != myVar)... 的程序员对我来说看起来很丑而且没有任何用处! 这是一个特殊的例子,可能是最常用的一般良好实践:如果您知道,请始终使用&lt;object that you know that is not null&gt;.equals(&lt;object that might be null&gt;);。如果您知道合约并且这些方法可以处理null 参数,则它适用于equals 以外的其他方法。 这是我看到的第一个真正有意义的Yoda Conditions 示例 NullPointerExceptions 抛出是有原因的。它们被抛出是因为一个对象不应该是空的。解决这个问题是程序员的工作,而不是隐藏问题。 这只是隐藏了问题。如果您稍后使用foo 怎么办?如果一个变量不应该为空,而是......您的应用需要获得 NPE,这样开发人员才能真正解决根本原因。【参考方案10】:

我已经尝试过NullObjectPattern,但对我来说并不总是最好的方法。有时“不采取行动”是不合适的。

NullPointerException 是一个运行时异常,这意味着它是开发人员的错,如果有足够的经验,它会告诉你错误的确切位置。

现在来回答:

尽量使您的所有属性及其访问器尽可能私有,或者完全避免将它们暴露给客户端。当然,您可以在构造函数中包含参数值,但是通过缩小范围,您不会让客户端类传递无效值。如果您需要修改这些值,您可以随时创建一个新的object。您只检查构造函数中的值一次,在其余方法中,您几乎可以确定这些值不为空。

当然,经验是理解和应用这个建议的更好方法。

字节!

【讨论】:

【参考方案11】:

Google 集合框架提供了一种很好且优雅的方式来实现空值检查。

在库类中有这样一个方法:

static <T> T checkNotNull(T e) 
   if (e == null) 
      throw new NullPointerException();
   
   return e;

而用法是(用import static):

...
void foo(int a, Person p) 
   if (checkNotNull(p).getAge() > a) 
      ...
   
   else 
      ...
   

...

或者在你的例子中:

checkNotNull(someobject).doCalc();

【讨论】:

嗯,有什么区别? p.getAge() 会以更少的开销和更清晰的堆栈跟踪抛出相同的 NPE。我错过了什么? 最好在你的例子中抛出一个 IllegalArgumentException("e == null") 因为它清楚地表明它是一个程序员意图的异常(以及足够的信息来实际允许维护者识别问题)。 NullPointerExceptions 应该为 JVM 保留,因为它清楚地表明这是无意的(并且通常发生在难以识别的地方) 现在是 Google Guava 的一部分。 对我来说闻起来像是过度设计。让 JVM 抛出一个 NPE,不要用这些垃圾把你的代码弄得乱七八糟。 我喜欢它并打开大多数方法和构造函数,并对参数进行显式检查;如果有错误,方法总是在前几行失败,我知道有问题的参考,但没有找到类似getThing().getItsThing().getOtherThing().wowEncapsulationIsBroken().setLol("hi");【参考方案12】:

哇,当我们有 57 种不同的方式来推荐 NullObject pattern 时,我几乎不想再添加另一个答案,但我认为一些对这个问题感兴趣的人可能想知道有一个关于 Java 7 的提案添加"null-safe handling"——if-not-equal-null 逻辑的简化语法。

Alex Miller 给出的例子是这样的:

public String getPostcode(Person person)   
  return person?.getAddress()?.getPostcode();  
  

?. 表示仅在左侧标识符不为空时取消引用,否则将表达式的其余部分计算为 null。有些人,比如 Java Posse 成员 Dick Wall 和 voters at Devoxx 真的很喜欢这个提议,但也有人反对,理由是它实际上会鼓励更多地使用 null 作为哨兵值。


更新:official proposal 用于 Java 7 中的 null 安全运算符已在 Project Coin. 下提交。语法与上面的示例略有不同,但概念相同。


更新: null-safe 运营商提案没有进入 Project Coin。因此,您不会在 Java 7 中看到这种语法。

【讨论】:

我认为这是错误的。应该有一种方法可以指定给定变量始终为非空。 更新:提案不会使Java7.见blogs.sun.com/darcy/entry/project_coin_final_five。 有趣的想法,但语法的选择很荒谬;我不希望每个关节都钉满问号的代码库。 这个运算符存在于Groovy中,所以想要使用它的人仍然可以选择它。 这是我见过的最巧妙的想法。它应该被添加到 C 语法的每一种合理的语言中。我宁愿在任何地方都“钉上问号”,而不是整天滚动屏幕或躲避“保护条款”。【参考方案13】:

最终,彻底解决这个问题的唯一方法是使用不同的编程语言:

在 Objective-C 中,您可以执行相当于在 nil 上调用方法的操作,但绝对不会发生任何事情。这使得大多数空检查变得不必要,但它会使错误更难诊断。 在Nice(Java 派生语言)中,所有类型都有两个版本:潜在空版本和非空版本。您只能在非空类型上调用方法。通过显式检查 null 可以将可能为 null 的类型转换为非 null 类型。这样可以更轻松地了解哪些地方需要进行空值检查,哪些地方不需要。

【讨论】:

我对 Nice 不熟悉,但 Kotlin 实现了相同的想法,在语言的类型系统中构建了可空和非空类型。比 Optionals 或 null Object 模式更简洁。【参考方案14】:

如果不允许使用未定义的值:

您可以配置您的 IDE 以警告您潜在的 null 取消引用。例如。在 Eclipse 中,请参阅 Preferences > Java > Compiler > Errors/Warnings/Null analysis

如果允许未定义的值:

如果您想定义一个新的 API,其中未定义的值有意义,请使用 Option Pattern(可能对函数式语言很熟悉)。它具有以下优点:

API 中明确说明输入或输出是否存在。 编译器会强制您处理“未定义”情况。 Option is a monad,因此不需要详细的空值检查,只需使用 map/foreach/getOrElse 或类似的组合器即可安全地使用值 (example)。

Java 8 有一个内置的Optional 类(推荐);对于早期版本,有库替代方案,例如Guava 的Optional 或FunctionalJava 的Option。但是像许多函数式模式一样,在 Java 中使用 Option(甚至 8 个)会产生相当多的样板文件,您可以使用不太冗长的 JVM 语言来减少这些样板文件,例如Scala 或 Xtend。

如果您必须处理可能返回空值的 API,那么在 Java 中您将无能为力。 Xtend 和 Groovy 有 Elvis operator ?: 和 null-safe dereference operator ?.,但请注意,如果引用为 null,则返回 null,因此它只是“推迟”对 null 的正确处理。

【讨论】:

确实,Option 模式很棒。存在一些 Java 等价物。 Guava 包含一个名为 Optional 的有限版本,它省略了大部分功能性内容。在 Haskell 中,这种模式称为 Maybe。 @Luca Molteni:你说的完全正确,我已经在帖子上发表评论请求扩展它。 :) Java 8 中将提供一个可选类 ...它(还)没有地图也没有平面地图:download.java.net/jdk8/docs/api/java/util/Optional.html 可选模式不能解决任何问题;而不是一个可能为空的对象,现在你有两个。【参考方案15】:

如果您使用(或计划使用)像 JetBrains IntelliJ IDEA、Eclipse 或 Netbeans 这样的 Java IDE 或像 findbugs 这样的工具,那么您可以使用注解来解决这个问题。

基本上,你有@Nullable@NotNull

你可以在方法和参数中使用,像这样:

@NotNull public static String helloWorld() 
    return "Hello World";

@Nullable public static String helloWorld() 
    return "Hello World";

第二个示例无法编译(在 IntelliJ IDEA 中)。

当你在另一段代码中使用第一个helloWorld()函数时:

public static void main(String[] args)

    String result = helloWorld();
    if(result != null) 
        System.out.println(result);
    

现在 IntelliJ IDEA 编译器会告诉您检查是无用的,因为 helloWorld() 函数永远不会返回 null

使用参数

void someMethod(@NotNull someParameter)  

如果你写这样的东西:

someMethod(null);

这不会编译。

最后一个使用@Nullable的例子

@Nullable iWantToDestroyEverything()  return null; 

这样做

iWantToDestroyEverything().something();

而且您可以确定这不会发生。 :)

这是一种让编译器比通常检查更多内容并强制执行更强大的合约的好方法。不幸的是,并非所有编译器都支持它。

在 IntelliJ IDEA 10.5 及更高版本中,他们添加了对任何其他 @Nullable @NotNull 实现的支持。

见博文More flexible and configurable @Nullable/@NotNull annotations

【讨论】:

@NotNull@Nullable 和其他空值注释是JSR 305 的一部分。您还可以使用它们来检测FindBugs 等工具的潜在问题。 我发现 @NotNull@Nullable 接口存在于包 com.sun.istack.internal 中,这很奇怪。 (我想我将 com.sun 与有关使用专有 API 的警告联系起来。) jetbrains 的代码可移植性无效。在绑定到 ide 级别之前我会三思而后行。就像 Jacek S 说的那样,它们是 JSR 的一部分,顺便说一句,我认为是 JSR303。 我真的不认为使用自定义编译器是解决这个问题的可行方案。 注解的好处,@NotNull@Nullable 是,当源代码由不理解它们的系统构建时,它们会很好地降级。因此,实际上,代码不可移植的论点可能是无效的 - 如果您使用支持并理解这些注释的系统,您将获得更严格的错误检查的额外好处,否则您得到的更少但您的代码仍然应该构建良好,并且运行程序的质量是相同的,因为无论如何这些注释都没有在运行时强制执行。此外,所有编译器都是自定义的 ;-)【参考方案16】:

对于实用程序类,您可以检查参数是否为空。

在所有其他情况下,您可能不必这样做。尽可能使用封装,从而减少您想要检查 null 的地方。

【讨论】:

【参考方案17】:

除了使用assert,您还可以使用以下内容:

if (someobject == null) 
    // Handle null here then move on.

这比:

if (someobject != null) 
    .....
    .....



    .....

【讨论】:

嗯,为什么?请不要有任何防备,我只是想了解更多关于 Java 的知识:) @Mudu 作为一般规则,我更喜欢 if 语句中的表达式是更“肯定”的语句,而不是“否定”语句。因此,如果我看到if (!something) x(); else y(); ,我会倾向于将其重构为if (something) y(); else x(); (尽管有人可能会争辩说!= null 是更积极的选择......)。但更重要的是,代码的重要部分没有包含在 s 中,并且对于大多数方法,您的缩进减少了一级。我不知道这是否是 fastcodejava 的推理,但那是我的。 这也是我倾向于做的事情。在我看来,保持代码整洁。 @MatrixFrog 是的,这使得代码更清晰易读,因为您首先看到了重要的代码(当一切正常运行时)。【参考方案18】:
public static <T> T ifNull(T toCheck, T ifNull) 
    if (toCheck == null) 
           return ifNull;
    
    return toCheck;

【讨论】:

这种方法有什么问题,我认为@tltester 只是想在它为空时给出一个默认值,这是有道理的。 Apache commons-lang中有这样一个方法:ObjectUtils.defaultIfNull()。还有一个更通用的:ObjectUtils.firstNonNull(),可以用来实现降级策略:firstNonNull(bestChoice, secondBest, thirdBest, fallBack);【参考方案19】:

另一个建议是防御性编程 - 您的类/函数提供已知且安全的默认值,而 null 保留用于真正的错误/异常。

例如,当出现问题(例如将数字转换为字符串)时,不要返回返回空字符串的函数,而是让它们返回空字符串 ("")。您仍然需要在继续之前测试返回值,但不会有例外的特殊情况。这种编程风格的另一个好处是您的程序将能够区分正常操作和异常并做出相应的响应。

【讨论】:

-1。那更糟。现在,如果您忘记测试而不是崩溃,您的程序会默默地传播不正确的值。【参考方案20】:

您可以使用FindBugs。他们还有一个Eclipse 插件)可以帮助您找到重复的空检查(除其他外),但请记住,有时您应该选择防御性编程。还有Contracts for Java 可能会有所帮助。

【讨论】:

【参考方案21】:

确实是 Java 中的常见“问题”。

首先,我对此的看法:

我认为在传递 NULL 时“吃”一些东西是不好的,而 NULL 不是有效值。如果您没有以某种错误退出该方法,那么这意味着您的方法没有出错,这是不正确的。然后你可能在这种情况下返回 null,在接收方法中你再次检查 null,它永远不会结束,你最终会得到 "if != null" 等等。

因此,恕我直言,null 必须是一个严重错误,它会阻止进一步执行(也就是说,null 不是有效值)。

我解决这个问题的方法是这样的:

首先,我遵循这个约定:

    所有公共方法/API 始终检查其参数是否为空 所有私有方法都不检查 null,因为它们是受控方法(如果上面没有处理,就让 nullpointer 异常死掉) 唯一不检查 null 的其他方法是实用程序方法。它们是公开的,但是如果你出于某种原因调用它们,你就会知道你传递了什么参数。这就像在不提供水的情况下试图在水壶中烧开水......

最后,在代码中,公共方法的第一行是这样的:

ValidationUtils.getNullValidator().addParam(plans, "plans").addParam(persons, "persons").validate();

注意addParam()返回self,这样可以添加更多参数进行检查。

如果任何参数为空,方法 validate() 将抛出选中的 ValidationException(选中或未选中更多是设计/品味问题,但我的 ValidationException 已选中)。

void validate() throws ValidationException;

例如,如果“plans”为空,则消息将包含以下文本:

"参数 [plans] 遇到非法参数值 null"

如您所见,用户消息需要 addParam() 方法中的第二个值(字符串),因为您无法轻松检测传入的变量名称,即使使用反射(无论如何都不是本文的主题.. .).

是的,我们知道,在这一行之外,我们将不再遇到空值,因此我们只需安全地调用这些对象上的方法。

这样,代码干净,易于维护和可读。

【讨论】:

当然。只是抛出错误和崩溃的应用程序质量更高,因为毫无疑问它们何时不工作。吞下错误的应用程序充其量会优雅地降级,但通常不会以难以注意到且无法修复的方式工作。当问题被发现时,它们就更难调试了。【参考方案22】:

我是“快速失败”代码的粉丝。问问自己 - 在参数为空的情况下,您是否在做一些有用的事情?如果您对代码在这种情况下应该做什么没有明确的答案......即它首先不应该为 null,然后忽略它并允许抛出 NullPointerException。调用代码对 NPE 的意义与对 IllegalArgumentException 的意义一样,但如果抛出 NPE,开发人员将更容易调试和理解出了什么问题,而不是您的代码试图执行一些其他意外的意外事件逻辑 - 最终导致应用程序失败。

【讨论】:

更好地使用断言,即 Contract.notNull(abc, "abc must be non-null, did it failed to load during xyz?"); - 这比 if (abc!=null) throw new RuntimeException... 更简洁【参考方案23】:

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

Avoiding Nulls with Polymorphic Dispatch Avoiding Nulls with "Tell, Don't Ask" Style

在文章中还有一个指向 Java Maybe 类型的 Git 存储库的链接,我觉得这很有趣,但我认为单独使用它并不能减少 检查代码膨胀。在网上做了一些研究后,我认为 != null 代码膨胀主要可以通过精心设计来减少。

【讨论】:

Michael Feathers 写了一篇简短而有趣的文章,介绍了您提到的方法:manuelp.newsblur.com/site/424 如果我对这个答案有什么最欣赏的地方,那就是“精心设计”的提示。无论归结为编写代码、修复错误还是检查无效性,在代码中进行整体良好的设计和组织是非常重要的,以避免由于长期以来做出的错误设计选择而导致我们今天仍然必须处理的许多冗余以前...【参考方案24】:

另一种选择:

下面的简单函数有助于隐藏空检查(我不知道为什么,但我没有发现它是同一个 common 库的一部分):

public static <T> boolean isNull(T argument) 
    return (argument == null);

你现在可以写了

if (!isNull(someobject)) 
    someobject.doCalc();

这是 IMO 表达!= null 的更好方式。

【讨论】:

如果你总是要对返回值取反,那么简单写一个函数isNotNull不是更好吗?这不是更清楚地表明了你的意图吗? @TMN:你说得对,理想情况下,我希望同时拥有 isNullisNotNull 方法。 我看不出这比“someobject != null”更简洁。 像 findbugs 这样的编译时和运行时开发工具将更难识别重复的空检查。另外,你最好希望Java内联isNull函数,【参考方案25】:

Null 不是“问题”。它是complete 建模工具集的组成部分。软件旨在模拟世界的复杂性,而 null 则承担着它的负担。 Null 在 Java 等中表示“无数据”或“未知”。因此,为这些目的使用空值是合适的。我不喜欢“空对象”模式;我认为它会引发“who will guard the guardians”问题。 如果你问我女朋友叫什么名字,我会告诉你我没有女朋友。在 Java 语言中,我将返回 null。 另一种方法是抛出有意义的异常以指示无法(或不想)解决的问题,并将其委托给堆栈中更高的位置以重试或向用户报告数据访问错误。

    对于“未知问题”,给出“未知答案”。(从业务角度来看,这是正确的 null 安全)在使用之前在方法内检查 null 参数可以减轻多个调用者的负担在通话前检查它们。

    public Photo getPhotoOfThePerson(Person person) 
        if (person == null)
            return null;
        // Grabbing some resources or intensive calculation
        // using person object anyhow.
    
    

    先前导致正常的逻辑流程从我的照片库中获取不存在的女朋友的照片。

    getPhotoOfThePerson(me.getGirlfriend())
    

    它适合新的 Java API(期待)

    getPhotoByName(me.getGirlfriend()?.getName())
    

    虽然对于某些人来说,查找存储在数据库中的照片是相当“正常的业务流程”,但我曾经在其他一些情况下使用如下所示的配对

    public static MyEnum parseMyEnum(String value); // throws IllegalArgumentException
    public static MyEnum parseMyEnumOrNull(String value);
    

    不要讨厌输入&lt;alt&gt; + &lt;shift&gt; + &lt;j&gt;(在Eclipse 中生成javadoc)并为您的公共API 写三个额外的词。除了不阅读文档的人之外,这对所有人来说已经足够了。

    /**
     * @return photo or null
     */
    

    /**
     * @return photo, never null
     */
    

    这是相当理论上的案例,在大多数情况下,您应该更喜欢 java null 安全 API(以防它会在 10 年后发布),但 NullPointerExceptionException 的子类。 因此,它是Throwable 的一种形式,表示合理的应用程序可能想要捕获的条件(javadoc)!要使用异常的第一个最大优势并将错误处理代码与“常规”代码 (according to creators of Java) 分开,对我来说,捕获 NullPointerException 是合适的。

    public Photo getGirlfriendPhoto() 
        try 
            return appContext.getPhotoDataSource().getPhotoByName(me.getGirlfriend().getName());
         catch (NullPointerException e) 
            return null;
        
    
    

    可能会出现问题:

    问。如果getPhotoDataSource() 返回 null 怎么办? A. 这取决于业务逻辑。如果我找不到相册,我不会给你看照片。如果 appContext 没有初始化怎么办?该方法的业务逻辑支持这一点。如果相同的逻辑应该更严格,那么抛出异常是业务逻辑的一部分,应该使用显式检查 null (案例 3)。 新的 Java Null-safe API 更适合这里有选择地指定什么暗示和不暗示被初始化在程序员错误的情况下快速失败。

    问。可以执行冗余代码并获取不必要的资源。 A. 如果getPhotoByName() 尝试打开数据库连接,创建PreparedStatement 并最终使用人名作为 SQL 参数,则可能会发生这种情况。 未知问题的方法给出了一个未知的答案(案例 1)在这里有效。在获取资源之前,该方法应检查参数并在需要时返回“未知”结果。

    问。由于尝试关闭打开,这种方法会降低性能。 A. 软件首先应该易于理解和修改。只有在此之后,才可以考虑性能,并且仅在需要时才考虑!和需要的地方! (source) 等)。

    附言。这种方法的使用与将错误处理代码与“常规”代码分开原则在某些地方使用是合理的一样合理。考虑下一个例子:

    public SomeValue calculateSomeValueUsingSophisticatedLogic(Predicate predicate) 
        try 
            Result1 result1 = performSomeCalculation(predicate);
            Result2 result2 = performSomeOtherCalculation(result1.getSomeProperty());
            Result3 result3 = performThirdCalculation(result2.getSomeProperty());
            Result4 result4 = performLastCalculation(result3.getSomeProperty());
            return result4.getSomeProperty();
         catch (NullPointerException e) 
            return null;
        
    
    
    public SomeValue calculateSomeValueUsingSophisticatedLogic(Predicate predicate) 
        SomeValue result = null;
        if (predicate != null) 
            Result1 result1 = performSomeCalculation(predicate);
            if (result1 != null && result1.getSomeProperty() != null) 
                Result2 result2 = performSomeOtherCalculation(result1.getSomeProperty());
                if (result2 != null && result2.getSomeProperty() != null) 
                    Result3 result3 = performThirdCalculation(result2.getSomeProperty());
                    if (result3 != null && result3.getSomeProperty() != null) 
                        Result4 result4 = performLastCalculation(result3.getSomeProperty());
                        if (result4 != null) 
                            result = result4.getSomeProperty();
                        
                    
                
            
        
        return result;
    
    

    PPS。对于那些快速投反对票(而且阅读文档不那么快)的人,我想说我一生中从未遇到过空指针异常(NPE)。但这种可能性是由 Java 创建者有意设计的,因为 NPE 是 Exception 的子类。我们在 Java 历史上有一个先例,ThreadDeathError 不是因为它实际上是一个应用程序错误,而仅仅是因为它不打算被捕获!与ThreadDeath 相比,Error 适合多少 NPE!但事实并非如此。

    仅在业务逻辑暗示时检查“无数据”。

    public void updatePersonPhoneNumber(Long personId, String phoneNumber) 
        if (personId == null)
            return;
        DataSource dataSource = appContext.getStuffDataSource();
        Person person = dataSource.getPersonById(personId);
        if (person != null) 
            person.setPhoneNumber(phoneNumber);
            dataSource.updatePerson(person);
         else 
            Person = new Person(personId);
            person.setPhoneNumber(phoneNumber);
            dataSource.insertPerson(person);
        
    
    

    public void updatePersonPhoneNumber(Long personId, String phoneNumber) 
        if (personId == null)
            return;
        DataSource dataSource = appContext.getStuffDataSource();
        Person person = dataSource.getPersonById(personId);
        if (person == null)
            throw new SomeReasonableUserException("What are you thinking about ???");
        person.setPhoneNumber(phoneNumber);
        dataSource.updatePerson(person);
    
    

    如果 appContext 或 dataSource 未初始化,未处理的运行时 NullPointerException 将终止当前线程并由Thread.defaultUncaughtExceptionHandler 处理(供您定义和使用您喜欢的记录器或其他通知机制)。如果未设置,ThreadGroup#uncaughtException 会将堆栈跟踪打印到系统错误。应该监视应用程序错误日志并为每个未处理的异常打开 Jira 问题,这实际上是应用程序错误。程序员应该在初始化东西的某个地方修复错误。

【讨论】:

捕获NullPointerException 并返回null 很难调试。无论如何,你最终都会得到 NPE,而且很难弄清楚最初是什么是空的。 如果我有声望,我会投反对票。 null 不仅不是必需的,而且是类型系统中的一个漏洞。将树分配给列表是类型错误,因为树不是列表类型的值;按照同样的逻辑,分配 null 应该是一个类型错误,因为 null 不是 Object 类型的值,也不是任何有用的类型。 Even the man that invented null 认为这是他的“十亿美元的错误”。 “一个可能是 T 或无类型值的值”的概念是它自己的类型,应该这样表示(例如,Maybe 或 Optional)。 从 "Maybe 或 Optional" 开始,您仍然需要编写像 if (maybeNull.hasValue()) ... 这样的代码,那么与 if (maybeNull != null)) ... 有什么区别? 至于“捕获 NullPointerException 并返回 null 对调试来说是可怕的。无论如何你最终都会遇到 NPE,而且很难弄清楚最初是什么是 null”。我完全同意!在这些情况下,如果业务逻辑暗示数据就地,您应该编写十几个“if”语句或抛出 NPE,或者使用新 Java 中的 null-safe 运算符。但是在某些情况下,我不关心什么确切的步骤给我 null。例如,当您确实希望数据可能丢失时,在屏幕上显示之前为用户计算一些值。 @MykhayloAdamovych:Maybe&lt;T&gt;Optional&lt;T&gt; 的好处并不在于您的T 可能为空,而是在它永远不应该为空的情况下。如果您有一个明确表示“此值可能为空 - 谨慎使用”的类型,并且您始终如一地使用和返回这种类型,那么每当您在您的代码,您可以假设它永远不会为空。 (当然,如果编译器可以强制执行,这将更加有用。)【参考方案26】:

Guava 是 Google 的一个非常有用的核心库,它有一个很好用的 API 来避免空值。我觉得UsingAndAvoidingNullExplained 很有帮助。

如 wiki 中所述:

Optional&lt;T&gt; 是一种将可以为空的 T 引用替换为 非空值。 Optional 可以包含非空 T 引用 (在这种情况下,我们说引用是“存在的”),或者它可能包含 什么都没有(在这种情况下,我们说引用是“不存在的”)。它从来不是 表示“包含空值”。

用法:

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

【讨论】:

@CodyGuldner 对,科迪。我提供了链接中的相关引用以提供更多背景信息。【参考方案27】:

您还可以使用 Checker Framework(JDK 7 及更高版本)静态检查空值。这可能会解决很多问题,但需要运行一个目前仅适用于 OpenJDK AFAIK 的额外工具。 https://checkerframework.org/

【讨论】:

【参考方案28】:

好的,我现在已经在技术上回答了一百万次,但我必须这样说,因为这是与 Java 程序员的无休止的讨论。

对不起,我几乎不同意以上所有内容。我们必须在 Java 中测试 null 的原因是因为 Java 程序员一定不知道如何处理内存。

我这样说是因为我有长期的 C++ 编程经验,而我们不这样做。换句话说,你不需要。请注意,在 Java 中,如果你碰到一个悬空指针,你会得到一个正常的异常;在 C++ 中,此异常通常不会被捕获并终止程序。

不想这样做?然后遵循一些简单的规则 ala C/C++。

不要轻易实例化事物,想想每个“新”都会给你带来很多麻烦,并遵循这些简单的规则。

一个类只能通过 3 种方式访问​​内存 ->

    它可以“拥有”类成员,他们将遵循以下规则:

      所有“HAS”成员都是在构造函数中“新建”的。 您将在析构函数或等效的 close() 中关闭 /de allocate 在 Java 中为同一个类而不是其他类中的函数。

这意味着您需要牢记(就像 Java 一样)每个资源的所有者或父级,并尊重该所有权。一个对象只会被创建它的类删除。还有->

    一些成员将被“使用”,但不是拥有或“拥有”。这是另一个类中的“OWN”,并作为参数传递给构造函数。由于这些属于另一个类,我们永远不会删除或关闭它,只有父类可以。

    类中的方法还可以实例化本地对象以供内部使用,这些对象永远不会从类中传递出去,或者它们应该是普通的“拥有”对象。

最后,要让所有这些工作,您需要有一个规范的设计,其中包含层次形式的类并且不产生循环。

在这种设计下,并遵循上述规则,层次设计中的子类永远不会访问被销毁的指针,因为这意味着父类在子类之前被销毁,而子类是非循环的设计不允许。

最后,还请记住,在启动系统时,您应该从上到下构建层次结构并从下到上破坏。你永远不会在任何地方有一个空指针,或者有人违反了规则。

【讨论】:

同意,尽管只是概念上的。是的,一个人应该有一个计划。但是,您需要能够制定计划并具有逻辑思考能力以及开发和维护一致模式的程序员。还有一些主要关注金钱和工资的雇主;-)。然后开始查看 JPA 规范,了解什么构成一致的模式/计划以及开发和/或记录它需要什么。 这个答案与 C++ 相关,但与 Java 这样的垃圾收集语言无关。 我不同意。 JAVA(或几乎任何其他语言)中良好的内存处理将为您带来很多好处。从减少空指针带来的更好的稳定性,降低内存占用带来的更好的性能,以及更好地控制关系的设计。我会建议许多 JAVA 应用程序马虎的内存管理导致频繁的 NPE 和内存泄漏。 我非常同意您写的内容,并认为非常不幸的是,Java 没有区分封装所有权的引用和不封装所有权的引用。那些争论“这就是 GC 的用途”的人错过了一个关键点:GC 避免了 不可变 对象拥有所有者的需要,并且它还 消除了 悬空的可能性对已删除对象的引用变成对其他任意对象的引用(在 C++ 中可能发生)。但是,如果多个对象将可变对象的状态封装为它自己的一部分,则很难编写正确的代码。 我更喜欢的想法是,引用可以封装身份、可变状态,或者两者都封装。此外,引用可以封装可变状态,因为它们标识了一个不可变对象,或者因为它们标识的对象永远不会暴露给任何可能改变它的东西。太糟糕了,Java 没有这种区别的概念,因为如果进行了平等检查和克隆等事情,99% 可以自动处理。【参考方案29】:

Java 7 有一个新的java.util.Objects 实用程序类,其中有一个requireNonNull() 方法。所有这一切都是在其参数为空时抛出一个NullPointerException,但它会稍微清理一下代码。示例:

Objects.requireNonNull(someObject);
someObject.doCalc();

该方法对checking在构造函数中赋值之前最有用,每次使用它可以节省三行代码:

Parent(Child child) 
   if (child == null) 
      throw new NullPointerException("child");
   
   this.child = child;

变成

Parent(Child child) 
   this.child = Objects.requireNonNull(child, "child");

【讨论】:

实际上,您的示例构成了代码膨胀:第一行是多余的,因为 NPE 将在第二行抛出。 ;-) 是的。一个更好的例子是如果第二行是doCalc(someObject) 视情况而定。如果您是 doCalc() 的作者,我建议将检查放入该方法的主体(如果可能)。然后你很可能会调用 someObject.someMethod() 再次不需要检查空值。 :-) 好吧,如果您不是doCalc() 的作者,并且在给定 null 时它不会立即抛出 NPE,那么您需要检查 null 并自己抛出 NPE。这就是Objects.requireNonNull() 的用途。 这不仅仅是代码膨胀。最好提前检查,而不是通过会导致副作用或使用时间/空间的方法进行一半。【参考方案30】:

我更喜欢这个

public void simpleFunc(SomeObject someObject)
    someObject = someObject != null ? someObject : new SomeObject(null);
    someObject.doSomething();

当然,在我的示例中 SomeObject 可以优雅地处理空参数。例如记录此类事件,然后什么都不做。

【讨论】:

以上是关于在 Java 中避免 NullPointerException的主要内容,如果未能解决你的问题,请参考以下文章

片段之间的共享元素转换

Xmpp String utils 经常使应用程序崩溃

在 Java 中避免同步(this)?

在 Java 中避免同步(this)?

在 java-8 中处理或避免 NPE 的最佳方法是啥?

在 Java 中避免 NullPointerException