番石榴检查 NotNull 有什么意义

我对番石榴很陌生(说实话,我不是“很陌生”,我在这个问题上是一个完全的菜鸟) ,所以我决定通过一些文件,并得到了相当惊讶当阅读这个:

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

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

myObject.getAnything();

(如果 myObject 为 null,则可能导致 NullPointerException)

我应该用

checkNotNull(myObject).getAnything();

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

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

这有什么意义? 这两条线和我能想到的任何情况下的结果都是一样的。

我甚至不认为后者更易读。

我肯定漏掉了什么,是什么?

45771 次浏览

myObject.getAnything();(如果 myObject 为 null,则可能导致 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

对于可为空的 myObject,而标准的点语法只有在 myObject为非空时才被允许。

这个想法就是快速失败。例如,考虑一下这个愚蠢的课程:

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之前不会。这可能不是您想要的; 作为该类的用户,我可以预期,如果它不接受 null,那么如果您传递一个 null,那么它的状态应该是不变的(换句话说: 调用应该失败,但它不应该使对象无效)。显然,在这个例子中,你也可以通过在递增 stringsSeen之前获得 s.length()来修复它,但是你可以看到对于一个更长更复杂的方法来说,首先检查你的所有参数是否有效,然后再修改状态可能是有用的:

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


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

几分钟前我已经读完了这个帖子。尽管如此,我还是不明白为什么我们要使用 checkNotNull。然后看一下番石榴的前置类文档,我得到了我想要的。过度使用 checkNotNull肯定会降低性能。

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

根据 DOC: < a href = “ http://google.github.io/guava/release/api/docs/com/google/common/base/Preconttions.html # checkNotNull (T)”rel = “ noReferrer”> Link

使用 checkNotNull:

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

关于性能的警告

此类的目标是提高代码的可读性,但在某些方面 在这种情况下,可能会付出巨大的性能代价。 请记住,消息构造的参数值必须全部为 自动装箱和 varargs 数组创建可能会发生 同样,即使先决条件检查成功了(也应该如此) 在某些情况下,这些浪费 CPU 周期和分配可能会导致真正的问题。 对性能敏感的前置条件检查总是可以转换为 习惯的形式:

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