什么是番石榴checkNotNull的重点

Ar3*_*r3s 64 java null preconditions guava

我是Guava的新手(说实话,我不是"非常新的",我是这个主题的新手)所以我决定阅读一些文档并在阅读时非常惊讶:

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

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

myObject.getAnything();
Run Code Online (Sandbox Code Playgroud)

(这可能导致NullPointerExceptionif myObject为null)

我应该用

checkNotNull(myObject).getAnything();
Run Code Online (Sandbox Code Playgroud)

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

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

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

我甚至认为后者更具可读性.

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

ysh*_*vit 95

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

public class Foo {
    private final String s;

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

    public int getStringLength() {
        return s.length();
    }
}
Run Code Online (Sandbox Code Playgroud)

假设你不想允许空值s.(否则getStringLength将抛出NPE).随着课程的原样,当你抓住它时null,已经太晚了 - 很难找到谁把它放在那里.罪魁祸首很可能是一个完全不同的阶级,而这个Foo例子可能是很久以前建造的.现在你必须梳理你的代码库,找出谁可能在null那里放置一个值.

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

public Foo(String s) {
    this.s = checkNotNull(s);
}
Run Code Online (Sandbox Code Playgroud)

现在,如果有人把一个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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

        stringsSeen++;
        totalLengthSeen += s.length();
    }
Run Code Online (Sandbox Code Playgroud)

  • 典型的模式是`this.s = checkNotNull(s);`带有静态导入. (12认同)
  • @HoàngLong我也希望`checkNotNull`会抛出IllegalArgumentException而不是NPE.对我来说,NPE意味着有人试图取消引用空指针 - 而不是某人在尝试取消引用它之前发现它为空的事实.但话虽如此,我也认为"IllegalArgumentException:foo为null"在任何实际意义上都不比"NullPointerException:foo"更有帮助.所以,虽然我有点希望Guava人选择IllegalArgumentException,但是`checkNotNull`的方便和标准化会覆盖那个,至少对我而言. (3认同)

maa*_*nus 9

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

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


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

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

您应该使用checkNotNull以便快速失败.如果没有它,您可以将非法传递null给另一个方法,该方法将其进一步传递,依此类推,等等,最终失败.那么你可能需要一些好运才能发现实际上第一种方法应该被拒绝null.


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

更新

其实,

 checkNotNull(myObject).getAnything()
Run Code Online (Sandbox Code Playgroud)

也是有道理的,因为你明确表达了不接受任何空值的意图.没有它,有人可能会认为你忘了支票并把它转换成类似的东西

 myObject != null ? myObject.getAnything() : somethingElse
Run Code Online (Sandbox Code Playgroud)

OTOH,我认为检查不值得冗长.在一个更好的语言中,类型系统会考虑可空性并给我们一些语义糖

 myObject!!.getAnything()                    // checkNotNull
 myObject?.getAnything()                     // safe call else null
 myObject?.getAnything() ?: somethingElse    // safe call else somethingElse
Run Code Online (Sandbox Code Playgroud)

对于可空的myObject,只有当myObject已知非空时才允许使用标准点语法.


iam*_*der 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
}
Run Code Online (Sandbox Code Playgroud)

关于性能的警告

此类的目的是提高代码的可读性,但是在某些情况下,这可能会付出巨大的性能代价。请记住,用于消息构造的参数值必须全部经过认真计算,并且即使先决条件检查成功后,自动装箱和varargs数组创建也可能会发生(因为它几乎总是在生产中进行)。在某些情况下,这些浪费的CPU周期和分配可能构成一个真正的问题。性能敏感的前提条件检查始终可以转换为惯用格式:

if (value < 0.0) {
     throw new IllegalArgumentException("negative value: " + value);
}
Run Code Online (Sandbox Code Playgroud)