处理Java中null的最佳方法?[关闭]


21

我有一些由于NullPointerException而失败的代码。在不存在该对象的对象上调用一个方法。

但是,这导致我考虑了解决此问题的最佳方法。我是否总是对空值进行防御性编码,以便将来为空指针异常证明代码,还是应该修复空值的原因,以免它在下游发生。

你怎么看?


5
为什么不“解决空值的原因”?您能否举例说明为什么这不是唯一明智的选择?显然,某些事情一定会使您远离显而易见的事物。它是什么?为什么不解决根源成为您的首选?
S.Lott

很好地解决问题的“根本原因”可能会在短期内有所帮助,但是如果代码仍未防御性地检查null并被可能引入null的另一个进程重用,则我必须再次对其进行修复。
Shaun F

2
@Shaun F:如果代码被破坏,那就被破坏了。我不知道代码怎么可能产生意外的空值而无法修复。显然,正在发生一些“愚蠢的管理”或“政治”的事情。或者某种允许错误代码以某种方式可以接受的东西。您能解释一下错误代码未得到修复的情况吗?导致您提出这个问题的政治背景是什么?
S.Lott

1
“以后可能会创建包含空值的数据”,这甚至意味着什么?如果“我可以修复数据创建例程”,则没有进一步的漏洞。我无法理解“此后具有空值的数据创建”的含义是什么?越野车软件?
S.Lott

1
@ S.Lott,封装和单一责任。您所有的代码都不“知道”您所有其他代码的含义。如果某个类接受Froobinator,它将对谁创建Froobinator以及如何创建做出尽可能少的假设。它来自“外部”代码,或者仅来自代码库的不同部分。我并不是说您不应该修复错误的代码;但是搜索“防御性编程”,您将找到OP所指的内容。
Paul Draper 2015年

Answers:


45

如果null是您方法的合理输入参数,请修复该方法。如果不是,请修复呼叫者。“合理”是一个灵活的术语,因此我建议进行以下测试:该方法应如何处理空输入?如果找到多个答案,则null 不是合理的输入。


1
就这么简单。
biziclop 2011年

3
番石榴有一些非常好的辅助方法,使null检查像Precondition.checkNotNull(...)。见stackoverflow.com/questions/3022319/...

我的规则是,如果可能的话,将所有内容初始化为合理的默认值,然后担心空异常。
davidk01 2011年

Thorbjørn:因此,它用故意的NullPointerException替换了意外的NullPointerException吗?在某些情况下,进行早期检查可能很有用,甚至有必要;但是我担心很多毫无意义的样板空检查会增加很小的价值,只会使程序变大。
user281377 2011年

6
如果null不是您方法的合理输入参数,但是方法的调用很可能会意外地传递null(尤其是如果该方法是公共的),那么IllegalArgumentException当给定null时,您可能还想让您的方法抛出一个。这会向方法的调用者发出信号,指出该错误位于其代码中(而不是方法本身)。
布莱恩

20

不要使用null,请使用Optional

正如您已经指出的那样,nullJava 的最大问题之一是它可以在任何地方使用,或者至少可以用于所有引用类型。

不可能说是可能null和什么不可能。

Java 8引入了一个更好的模式:Optional

来自Oracle的示例:

String version = "UNKNOWN";
if(computer != null) {
  Soundcard soundcard = computer.getSoundcard();
  if(soundcard != null) {
    USB usb = soundcard.getUSB();
    if(usb != null) {
      version = usb.getVersion();
    }
  }
}

如果这些方法中的每一个都可能返回成功值或可能不返回成功值,则可以将API更改为Optionals:

String name = computer.flatMap(Computer::getSoundcard)
    .flatMap(Soundcard::getUSB)
    .map(USB::getVersion)
    .orElse("UNKNOWN");

通过在类型中显式编码可选性,您的接口将变得更好,并且代码将变得更简洁。

如果您未使用Java 8,则可以com.google.common.base.Optional在Google Guava中查看。

番石榴团队的一个很好的解释: https //github.com/google/guava/wiki/UsingAndAvoidingNullExplained

有关null缺点的更一般的解释,并提供几种语言的示例:https : //www.lucidchart.com/techblog/2015/08/31/the-worst-mistake-of-computer-science/


@ Nonnull,@ Nullable

Java 8添加了这些注释,以帮助诸如IDE的代码检查工具发现问题。它们的效果相当有限。


检查什么时候有意义

不要写50%的代码检查是否为null,尤其是如果没有明智的代码可以使用 null值。

另一方面,如果null可以使用并且表示某些意思,请确保使用它。


最终,您显然无法null从Java中删除。我强烈建议Optional在可能的情况下替换该抽象,并null在其他时候检查您可以对其进行合理的处理。


2
通常,由于质量低下,对四年老问题的答案最终会被删除。谈论Java 8(当时还不存在)如何解决该问题,做得很好。

但与原来的问题无关。而且该参数确实是可选的吗?
jwenting

5
@jwenting与原始问题完全相关。“用螺丝刀钉子的最佳方法是什么”的答案是“用锤子代替螺丝刀”。
2015年

@jwenting,我想我涵盖了这一点,但是为了清楚起见:参数不是可选的,我通常不会检查null。您将有100行空值检查和40行实质内容。在更公共的接口上检查null,或者在实际有意义的时候检查null(即,该参数是可选的)。
Paul Draper 2015年

4
@ J-Boss,在Java中,您如何不做检查NullPointerException每次您在Java中调用实例方法时,A NullPointerException确实会发生 。您几乎可以在每种方法中使用。throws NullPointerException
保罗·德雷珀

8

有多种方法可以解决此问题,将代码添加到代码if (obj != null) {}中并不理想,因为它很杂乱,在维护周期后期读取代码时会增加噪音,并且容易出错,因为很容易忘记做此样板包装。

这取决于您是否希望代码继续安静地执行或失败。是null错误还是预期条件。

什么是null

在每种定义和情况下,Null表示绝对缺少数据。数据库中的空值表示该列缺少值。从理论上讲,null String与null 不相同String,null int与零不同。在实践中,“取决于”。空String可以Null Object为String类提供良好的实现,因为Integer它取决于业务逻辑。

替代方法是:

  1. Null Object模式。创建代表null状态的对象实例,并使用对Null实现的引用初始化对该类型的所有引用。这对于没有太多引用其他对象的简单值类型对象很有用,其他对象也可能是null并且被期望是null有效状态。

  2. 使用面向方面的工具来编织具有Null Checker防止参数为空的方面的方法。这是针对null出现错误的情况。

  3. 使用并assert()不会比if (obj != null){}噪音少多少而已。

  4. 使用合同执行工具,例如Java合同。与AspectJ之类的用例相同,但较新,使用注释而不是外部配置文件。方面和断言两全其美。

当已知传入数据null并且需要用一些默认值替换传入数据时,1是理想的解决方案,这样上游使用者就不必处理所有空检查样板代码。根据已知的默认值进行检查也将更具表现力。

2、3和4只是方便的备用Exception生成器,可以替换NullPointerException为更多有用的信息,这始终是改进。

到底

nullJava几乎在所有情况下都是逻辑错误。您应该始终努力消除根源NullPointerExceptions。您应该努力不要将null条件用作业务逻辑。if (x == null) { i = someDefault; }只需对该默认对象实例进行初始分配即可。


如果可能的话,则使用null对象模式,这是处理null情况的最合适的方法。很少有人希望使用null来导致产品在生产中爆炸!
理查德·米斯金

@Richard:除非那null是意外的,否则这是一个真正的错误,那么所有事情都应该完全停止。

数据库和编程中的Null在一个重要方面的处理方式有所不同:在编程中null == null(甚至在PHP中),但在数据库中,null != null因为在数据库中,Null 表示未知值,而不是“无”。两个未知数不一定相等,而两个虚数却相等。
西蒙·佛斯伯格

8

添加空检查可能会使测试成为问题。看这个伟大的演讲...

查看Google Tech讲座演讲:“干净的代码讲座-别找东西!” 他在24分钟左右谈到

http://www.youtube.com/watch?v=RlfLCWKxHJ0&list=PL693EFD059797C21E

偏执编程涉及到处添加空检查。起初似乎是个好主意,但是从测试的角度来看,它使您的空检查类型测试变得难以处理。

同样,当您为某些对象的存在创建前提条件时,例如

class House(Door door){

    .. null check here and throw exception if Door is NULL
    this.door = door
}

它会阻止您创建House,因为您将引发异常。假设您的测试用例正在创建模拟对象以测试除Door以外的其他东西,那么您不能执行此操作,因为需要Door

那些因创建Mock而遭受痛苦的人都清楚地知道这些烦恼。

总而言之,您的测试套件应该足够健壮,可以对门,房屋,屋顶或其他任何东西进行测试,而不必对此感到偏执。严重地,为测试中的特定对象添加空检查测试有多困难:)

您应该始终喜欢能​​正常工作的应用程序,因为您有多个测试证明它可以工作,而不是仅仅因为您在整个地方都有一堆前提条件的空检查而跳了起来才有效


4

tl; dr-检查是否有意外情况是好的null s很好,但是应用程序尝试使它们变好是不好的。

细节

显然,在某些情况下 null方法的有效输入或输出,而在其他情况下则无效。

规则1:

允许null参数或返回null值的方法的javadoc 必须清楚地记录下来,并说明其null含义。

规则2:

null除非有充分的理由,否则不应将API方法指定为接受或返回。

给定一个方法相对于nulls 的明确约定,在不应该传递或返回a的地方是编程错误null

规则3:

应用程序不应尝试“弥补”编程错误。

如果方法检测到 null到不应存在的方法,则不应尝试通过将其转换为其他方法来解决该问题。这只是向程序员隐藏了问题。相反,它应该允许NPE发生并引起故障,以便程序员可以找出根本原因并加以解决。希望在测试过程中会发现故障。如果没有,那就说明您的测试方法。

规则4:

在可行的情况下,编写代码以及早发现编程错误。

如果您的代码中存在导致大量NPE的错误,那么最困难的事情就是找出null值的来源。简化诊断的一种方法是编写代码,以便null尽快检测到。通常,您可以结合其他检查来执行此操作;例如

public setName(String name) {
    // This also detects `null` as an (intended) side-effect
    if (name.length() == 0) {
        throw new IllegalArgumentException("empty name");
    }
}

(显然,在某些情况下应根据实际情况调整规则3和4。例如(规则3),某些类型的应用程序必须在检测到可能的编程错误后尝试继续运行。(规则4)过多检查不良参数可能会导致错误。对性能的影响。)


3

我建议将方法固定为防御性的。例如:

String go(String s){  
    return s.toString();  
}

应该更像这样:

String go(String s){  
    if(s == null){  
       return "";  
    }     
    return s.toString();  
}

我意识到这绝对是微不足道的,但是如果调用者期望一个对象给他们一个默认对象,则不会导致传递空值。


10
这有一个讨厌的副作用,即调用者(使用此API进行编程的程序员)可能养成将null作为参数传递以获得“默认”行为的习惯。
Goran Jovic

2
如果这是Java,则完全不应该使用new String()
biziclop 2011年

1
实际上,@ biziclop比大多数人意识到的重要得多。
Woot4Moo 2011年

1
在我看来,如果参数为null,则放置断言并引发异常更为有意义,因为这显然是调用者的错误。不要默默地“修复”呼叫者错误;而是让他们意识到它们。
安德列斯·F

1
@ Woot4Moo因此,编写自己的代码,然后抛出异常。重要的是要通知调用者他们传递的参数是错误的,并尽快告诉他们。静默校正nulls是最糟糕的选择,而不是抛出NPE。
Andres F.

2

到目前为止,以下有关null的一般规则对我有很大帮助:

  1. 如果数据来自您的控制之外,请系统检查空值并采取适当措施。这意味着要么抛出对函数有意义的异常(选中或未选中,只需确保异常的名称告诉您发生了什么。)。但是绝对不要在您的系统中散布可能潜在地带来意外的价值。

  2. 如果Null在数据模型的适当值范围内,请进行适当处理。

  3. 返回值时,尽量不要返回null。始终喜欢空列表,空字符串,空对象模式。当这是给定用例的最佳数据表示形式时,请将空值保留为返回值。

  4. 可能是最重要的……测试,再测试。在测试您的代码时,请不要像编码员一样对其进行测试,而应将其作为纳粹精神病患者的母体进行测试,并尝试想象各种各样的方法来折磨该代码。

在零点偏执方面,这往往会导致空虚,这通常会导致外观和代理将系统与外界接口,并在内部严格控制值并具有大量冗余。这里的外界几乎意味着我没有编写自己的所有代码。它的确耗费了运行时间,但是到目前为止,我很少需要通过创建代码的“空安全段”来优化它。我必须说,尽管我主要是为医疗保健创建长期运行的系统,但我最后想要的是接口子系统将您的碘过敏携带到CT扫描器崩溃,因为意外的空指针是因为另一个系统中的其他人从未意识到名称可能包含撇号或诸如耒耨的字符。

反正...我的2美分


1

我建议从功能语言中使用Option / Some / None模式。我不是Java专家,但是我在C#项目中大量使用了我自己的这种模式的实现,并且我确信它可以转换为Java世界。

该模式背后的想法是:如果逻辑上存在可能缺少某个值的情况(例如,通过id从数据库检索时),则提供一个Option [T]类型的对象,其中T是一个可能的值。如果缺少值,则返回没有类None [T]的值对象,如果返回的话-包含值的Some [T]对象返回。

在这种情况下,您必须处理价值缺失的可能性,并且如果您进行代码检查,则很容易发现错误处理的位置。要从C#语言实现中获得启发,请参阅我的bitbucket存储库 https://bitbucket.org/mikegirkin/optionsomenone

如果返回空值,并且在逻辑上等效于错误(例如,不存在文件或无法连接),则应引发异常或使用其他错误处理模式。同样,此背后的想法最终导致解决方案,当您必须处理价值缺失的情况时,可以轻松地在代码中找到错误处理的地方。


0

好吧,如果方法的可能结果之一是空值,那么您应该为此防御性地进行编码,但是,如果方法应该返回非空值,但我不能肯定会解决这个问题。

和往常一样,视情况而定,就像生活中的大多数事情一样:)



0
  1. 请勿使用Optional类型,除非它确实是可选的,否则通常最好将退出作为异常处理,除非您确实希望将null作为选项,而不是因为您经常编写错误的代码。

  2. 正如Google的文章所指出的那样,问题不是null,因为它有其用途。问题在于应该检查和处理空值,通常可以优雅地退出它们。

  3. 在程序的常规操作之外,有许多无效情况表示无效条件(无效的用户输入,数据库问题,网络故障,丢失的文件,损坏的数据),这是检查异常的目的,处理它们,甚至如果只是记录它。

  4. 异常处理允许在JVM中使用不同的操作优先级和特权,而不是像Optional这样的类型,这种异常和异常的发生具有特殊的意义,包括早期支持优先处理程序延迟加载到内存中,因为您不这样做。需要它一直挂在身边。

  5. 您无需在可能会发生的地方,可能访问不可靠数据服务的任何地方编写空处理程序,并且由于其中大多数都属于可以轻易抽象的几种通用模式,因此您只需要处理程序调用,但极少数情况除外。

因此,我想我的答案是将其包装在受检查的异常中并对其进行处理,或者解决不可靠的代码(如果它在您的能力范围内)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.