【问题标题】:What's the point of Guava checkNotNull番石榴 checkNotNull 有什么意义
【发布时间】:2014-10-03 18:05:50
【问题描述】:

我对 Guava 很陌生(老实说,我不是“很新”,我是该主题的新手),所以我决定浏览一些文档并在阅读本文时感到非常惊讶:

com.google.common.base.Preconditions.checkNotNull(...)

我不明白这种方法的意义。 这意味着不是这样做:

myObject.getAnything();

(如果 myObject 为空,可能会导致 NullPointerException

我应该使用

checkNotNull(myObject).getAnything();

如果myObject 为空,抛出NullPointerException,如果不为空,则返回myObject

我很困惑,这可能是有史以来最愚蠢的问题,但是......

这有什么意义? 考虑到我能想到的任何情况,这两行代码的作用与结果完全相同。

我什至不认为后者更具可读性。

所以我一定错过了什么。这是什么?

【问题讨论】:

  • 我并不认同在每个地方都添加 checkNotNull 的想法。恕我直言,如果您明确地验证您的输入,而不是过度依赖有助于混淆您的代码的语法糖,那会更可取。快速失败是可以的,但 checkNotNull 只是没有提供任何其他信息用于故障排除 --> 您可以简单地为错误的输入格式设置自己的异常,并输入一条很好的消息来解释具体情况
  • 我完全同意这很荒谬。在我看来,检查 null 然后自己抛出 NPE 完全是疯了。至少抛出一个 ValidationException 或 IllegalArgumentException。
  • 考虑使用checkNotNull(reference, errorMessageTemplate, errorMessageArgs)提供更好的诊断:guava.dev/releases/23.0/api/docs/com/google/common/base/…

标签: java null guava preconditions


【解决方案1】:

我们的想法是快速失败。例如,考虑这个愚蠢的类:

public class Foo {
    private final String s;

    public Foo(String s) {
        this.s = s;
    }

    public int getStringLength() {
        return s.length();
    }
}

假设您不想为s 提供空值。 (否则getStringLength 将抛出 NPE)。按原样上课,当你看到null 时,已经太晚了——很难找出是谁把它放在那里的。罪魁祸首很可能在一个完全不同的类中,并且 Foo 实例可能是很久以前构建的。现在你必须梳理你的代码库,找出谁可能在其中放置了 null 值。

相反,想象一下这个构造函数:

public Foo(String s) {
    this.s = checkNotNull(s);
}

现在,如果有人在其中输入 null,您会立即发现 -- 并且堆栈跟踪将准确地指向出错的调用。


如果您想在采取可以修改状态的操作之前检查参数,这可能很有用。例如,考虑这个类,它计算它得到的所有字符串长度的平均值:

public class StringLengthAverager {
    private int stringsSeen;
    private int totalLengthSeen;

    public void accept(String s) {
        stringsSeen++;
        totalLengthSeen += s.length();
    }

    public double getAverageLength() {
        return ((double)totalLengthSeen) / stringsSeen;
    }
}

调用accept(null) 将导致NPE 被抛出——但在stringsSeen 增加之前不会。这可能不是您想要的;作为该类的用户,我可能期望如果它不接受空值,那么如果传递空值,它的状态应该保持不变(换句话说:调用应该失败,但它不应该使对象无效)。显然,在此示例中,您还可以通过在增加 stringsSeen 之前获取 s.length() 来修复它,但是您可以看到对于更长且更复杂的方法,首先检查所有参数是否有效可能很有用,并且然后才修改状态:

    public void accept(String s) {
        checkNotNull(s); // that is, s != null is a precondition of the method

        stringsSeen++;
        totalLengthSeen += s.length();
    }

【讨论】:

  • 不应该是更多 this.s=Preconditions.checkNotNull(s);
  • 但是是的,我没有考虑存储箱。
  • 典型的模式是this.s = checkNotNull(s);,带有静态导入。
  • 个人认为这种报错方式应该避免。无论您使用什么技巧,NullPointerException 从字面上看几乎没有意义。我强烈反对在任何函数中传递/返回 null(除非您使用的是旧版 API),因此任何这样做的努力都应该让 WrongApiUsage 异常出现。
  • @HoàngLong 我也希望checkNotNull 会抛出 IllegalArgumentException,而不是 NPE。对我来说,NPE 意味着有人试图取消引用一个空指针——并不是说有人在尝试取消引用它之前发现它是空的。但话虽如此,我也认为“IllegalArgumentException: foo was null”在任何实际意义上并不比“NullPointerException: foo”更有帮助。所以,虽然我有点希望 Guava 人选择了 IllegalArgumentException,但 checkNotNull 的便利性和标准化覆盖了这一点,至少对我而言。
【解决方案2】:

myObject.getAnything();(如果 myObject 为 null,可能会导致 NullPointerException)

不...它myObject == null 时抛出 NPE。在 Java 中,不可能调用带有null 接收器的方法(理论上的例外是静态方法,但它们可以而且应该总是在没有任何对象的情况下调用)。


我应该使用checkNotNull(myObject).getAnything();

不,你不应该。这将是相当多余的(更新)。

您应该使用checkNotNull快速失败。没有它,您可能会将非法的null 传递给另一个方法,该方法会进一步传递它,依此类推,最终失败。然后你可能需要一些好运才能发现实际上第一个方法应该拒绝null


yshavit 的回答提到了一个重要的点:传递非法值是不好的,但存储它并在以后传递它更糟糕。

更新

其实

 checkNotNull(myObject).getAnything()

也很有意义,因为您清楚地表达了不接受任何空值的意图。没有它,有人可能会认为您忘记了支票并将其转换为类似

 myObject != null ? myObject.getAnything() : somethingElse

OTOH,我认为支票不值得冗长。在better language 中,类型系统会考虑可空性并给我们一些语义糖,例如

 myObject!!.getAnything()                    // checkNotNull
 myObject?.getAnything()                     // safe call else null
 myObject?.getAnything() ?: somethingElse    // safe call else somethingElse

对于可以为空的myObject,而标准点语法仅在myObject 已知为非空时才允许使用。

【讨论】:

  • 这就像在方法的开头检查该方法的前提条件是否有效?
  • 没错。立即检查非法参数可确保您以后不必追捕它们。此外,每个查看该方法的人都会看到被禁止的内容(当然,应该记录在案......)。
  • @Juru:是的,这就是它在 Preconditions 类中的原因!
【解决方案3】:

几分钟前我已经阅读了整个帖子。尽管如此,我还是很困惑我们为什么要使用checkNotNull。然后查看 Guava 的 Precondition 类文档,我得到了我的预期。过度使用checkNotNull 肯定会降低性能。

我的想法是checkNotNull 方法对于直接来自用户的数据验证 或与用户交互的最终 API 是值得的。它不应该在内部 API 的每个方法中都使用,因为使用它你不能停止异常,而是更正你的内部 API 以避免异常。

根据 DOC: Link

使用 checkNotNull:

public static double sqrt(double value) {
     Preconditions.checkArgument(value >= 0.0, "negative value: %s", value);
     // calculate the square root
}

性能警告

这个类的目标是提高代码的可读性,但在某些 在这种情况下,这可能会带来巨大的性能成本。 请记住,消息构造的参数值必须全部为 急切地计算,并且可能会发生自动装箱和可变参数数组创建 同样,即使前提条件检查成功(因为它应该 几乎总是在生产中做)。在某些情况下,这些浪费 CPU 周期和分配可能会成为一个真正的问题。 性能敏感的前置条件检查总是可以转换为 习惯形式:

if (value < 0.0) {
     throw new IllegalArgumentException("negative value: " + value);
}

【讨论】:

  • 给出 -1,抱歉:我不建议将其用于用户数据验证,因为您会希望为用户提供更加用户友好的非技术错误消息。此外,运行时异常也不打算在预期会发生不稳定情况的情况下发生。用户可能会不时犯错。我建议在公共方法中检查方法参数,而不是在私有方法中。尽早发现编程错误比过早优化更有价值。仅当您发现它是一个实际的性能问题时才替换前置条件检查。
  • @HennoVermeulen 正如你所说的“检查公共方法中的方法参数”,我还告诉过“它不应该在内部 API 的每个方法中都使用”。谢谢
  • 赞成在性能方面保持领先,但正如其他人所说,这不是用户验证的好方法!
猜你喜欢
  • 1970-01-01
  • 2012-06-04
  • 1970-01-01
  • 2011-05-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多