番石榴checkNotNull有什么意义


70

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

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

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

myObject.getAnything();

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

我应该用

checkNotNull(myObject).getAnything();

抛出NullPointerException如果myObject是空和返回myObject,如果它不为空。

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

这有什么意义呢?在我可以想到的任何情况下,这两行所做的事情与结果完全一样。

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

所以我一定想念一些东西。它是什么?


1
我真的不赞成在每个地方都添加checkNotNull的想法。恕我直言,如果您明确验证输入内容,而不是过分依赖有助于混淆代码的语法糖,那将是更好的选择。快速失败是正常的,但checkNotNull只是不给任何其他信息进行故障诊断- >您可以简单地使自己的错误输入格式异常,并把在一个不错的消息,说明具体情况
晃龙

我完全同意这是荒谬的。以我的拙见,检查null并自己抛出NPE完全是疯狂的。至少抛出ValidationException或IllegalArgumentException。
劳伦斯

考虑通过以下方式提供更好的诊断checkNotNull(reference, errorMessageTemplate, errorMessageArgs)guava.dev/releases/23.0/api/docs/com/google/common/base/…–
Vadzim

Answers:


105

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

public class Foo {
    private final String s;

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

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

假设您不想允许使用null值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增加之前不会发生。这可能不是您想要的;作为类的用户,我可能希望,如果它不接受空值,那么如果您传递空值,则其状态应该保持不变(换句话说:调用应该失败,但不应使对象无效)。显然,在此示例中,您也可以通过s.length()在递增之前获取来解决此问题stringsSeen,但是您可以看到对于更长且涉及更多的方法,首先检查所有参数是否有效,然后再修改状态可能是有用的:

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

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

1
s = Preconditions.checkNotNull(s);
Ar3s 2014年

但是,是的,我没有考虑储物箱。
Ar3s 2014年

13
典型的模式是this.s = checkNotNull(s);静态导入。
ColinD 2014年

1
我个人认为应该避免这种错误报告。无论您做什么技巧,NullPointerException字面意义都几乎没有。我强烈反对在ANY函数中传递/返回null(除非您使用的是旧版API),因此,这样做的任何努力都应该使WrongApiUsage异常出现。
霍隆2015年

3
我还希望@HoàngLongcheckNotNull抛出一个IllegalArgumentException而不是NPE。对我来说,NPE意味着有人试图取消引用空指针,而不是有人试图取消引用空指针。话虽这么说,但我也认为从任何实际意义上讲,“ IllegalArgumentException:foo为空”并不比“ NullPointerException:foo”更有用。因此,尽管我有点希望番石榴族人选择了IllegalArgumentException,但checkNotNull至少对于我来说,替代的便利性和标准化。
yshavit '16

10

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

不,只要有,它就会抛出NPE myObject == null。在Java中,没有机会使用null接收方调用方法(理论上是静态方法,但是可以并且应该始终在没有任何对象的情况下调用它们)。


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

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

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


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

更新资料

其实,

 checkNotNull(myObject).getAnything()

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

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

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

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

为nullable myObject,而仅当myObject已知非null时才允许使用标准点语法。


这就好比在方法开始时检查关于该方法的前提条件是否有效吗?
朱鲁

究竟。立即检查是否有非法论点,确保您以后不必再寻找它们。而且,每个查看该方法的人都会看到禁止的内容(请确保将其记录为文档...)。
maaartinus 2014年

@Juru:是的,这就是为什么它在Preconditions课堂上!
ColinD 2014年

5

几分钟前,我已经阅读了整个主题。但是,我很困惑为什么要使用checkNotNull。然后查看Guava的Precondition类文档,我得到了我所期望的。过量使用checkNotNull肯定会降低性能。

我的想法是checkNotNull数据验证需要有价值的方法,它直接来自用户,也可能来自最终的API与用户交互。不应在内部API的每种方法中使用它,因为使用它您不能停止异常,而是更正内部API以避免Exception。

根据DOC: 链接

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

1
给出-1,抱歉:我不建议将其用于用户数据验证,因为您将希望为用户提供更加用户友好的非技术性错误消息。另外,如果可以预期会发生不稳定情况,则也不打算提供运行时异常。可以期望用户时常犯错。我建议在公共方法中而不是在私有方法中检查方法参数。尽早发现编程错误比过早优化更有价值。仅在发现先决条件检查是实际性能问题时,才将其替换。
Henno Vermeulen

@HennoVermeulen正如您所说的“在公共方法中检查方法参数”,我还告诉“不应在内部API的每个方法中都使用它”。谢谢
iamcrypticcoder

赞成提高性能,但正如其他人所说,这不是用户验证的好方法!
Tullochgorum
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.