73

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

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

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

myObject.getAnything();

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

我应该使用

checkNotNull(myObject).getAnything();

抛出一个NullPointerExceptionifmyObject为 null 并myObject在它不为 null 时返回。

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

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

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

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

4

3 回答 3

109

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

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);
}

现在,如果有人把 a放在那里,你会马上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增加之前。这可能不是您想要的;作为该类的用户,我可能期望如果它不接受空值,那么如果传递空值,它的状态应该保持不变(换句话说:调用应该失败,但它不应该使对象无效)。显然,在此示例中,您也可以通过s.length()在递增之前获取来修复它stringsSeen,但是您可以看到对于更长和更复杂的方法,首先检查所有参数是否有效,然后才修改状态可能很有用:

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

        stringsSeen++;
        totalLengthSeen += s.length();
    }
于 2014-10-03T18:16:19.387 回答
10

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

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


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

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

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


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

更新

实际上,

 checkNotNull(myObject).getAnything()

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

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

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

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

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

于 2014-10-03T18:14:07.387 回答
6

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

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

根据 DOC: 链接

使用 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);
}
于 2017-03-06T17:05:52.000 回答