it-swarm.cn

处理Java中null的最佳方法?

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

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

你怎么看?

21
Shaun F

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

45
user281377

不要使用null,请使用Optional

正如您所指出的,Java中的null的最大问题之一是可以在到处处使用它,或者至少对所有引用类型都可以使用它。 。

不可能说可能是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,则可以查看Google Guava中的com.google.common.base.Optional

番石榴团队的很好解释: 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并表示某些含义,请确保使用它。


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

21
Paul Draper

有多种方法可以解决此问题,用if (obj != null) {}填充代码不是很理想,这很杂乱,在维护周期稍后读取代码时会增加噪音,并且容易出错,因为很容易忘记做这个锅炉板包装。

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

什么是null

在每种定义和情况下, Null 表示绝对缺少数据。数据库中的空值表示该列缺少值。从理论上讲,空String与空String不同,空int与零不同。在实践中,“取决于”。空String可以为String类提供良好的_Null Object_实现,因为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,这始终是改进。

到底

null in Java几乎在所有情况下都是逻辑错误。您应始终努力消除NullPointerExceptions的根本原因。您应努力不使用null条件作为业务逻辑; if (x == null) { i = someDefault; }只是对该默认对象实例进行初始分配。

8
user7519

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

查看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而遭受痛苦的人都清楚地知道这些烦恼。

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

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

8
Constantin

tl; dr-检查意外的nulls很好,但是对于应用程序来说,使它们变好是不好的。

详细信息

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

规则1:

允许null参数或返回null必须的方法的javadoc对此进行了清晰的记录,并进行了解释null是什么意思。

规则2:

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

给定一个方法相对于nulls的“合同”的明确说明,传递或返回一个编程错误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)过多检查不良参数可能会对性能产生影响。)

4
Stephen C

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

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

应该更像这样:

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

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

3
Woot4Moo

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

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

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

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

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

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

反正....我的2美分

2
Newtopian

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

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

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

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

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

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

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

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

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

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

0
J-Boss

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

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

0
jonezy

Apache Commons的 lang 库提供了一种处理空值的方法

ObjectUtils 中的方法 defaultIfNull 使您可以在传递的对象为null时返回默认值

0
Mahmoud Hossam