是的,没关系,但我认为让它发生是一个更好的决定。
Java 程序员和 null 的问题在于人们来自 C/C++ 背景,其中 NULL 意味着很多不同的东西。在 C/C++ 中,取消引用 NULL(或 Wild)指针是一个严重的问题,可能会导致奇怪的内存问题或使您的程序崩溃(显然是不可取的)。如果您能够摆脱 C/C++ 的思维方式,并且意识到您有这个额外的层,即 JVM,它会为您处理这种情况,您就会开始对 NULL 有一点不同的看法。
例如,在 C++ 中,我们有永远不能赋值为 NULL 的引用。在 Java 中,没有引用,但 Java 对象参数的行为更像 C++ 指针。但是在 Java 中有很多情况,其中方法隐式不应该接收参数的空值!那么我们该怎么办?
像在 C++ 中那样在 Java 中处理 null 的问题在于,这会导致到处检查 null ,而在 C++ 中,您只需声明一个获取引用的方法,该方法明确声明它不接受 NULL。很快,每个方法都必须在其中进行健全性检查,以便在那时断言程序的条件,从而造成混乱。
在方法的默认约定是 null 对参数值无效的假设下工作是一种更好的心态。
为什么?好吧,让我们看看当这样的方法接收 null 作为参数值时会发生什么。a) 也许没关系,因为它不会取消引用该值作为其行为的一部分。在这种情况下,什么都不会发生。b) 该值被取消引用。在这种情况下,来自 JVM 的行为正是我们想要的:抛出一个异常,表明由于参数值为 null 而违反了方法的约定,并且它包含一个堆栈跟踪,将我们一直带到以这种方式使用该值的方法中的行。
人们对 NPE 提出质疑,因为他们认为当您在日志中看到 NPE 时,这意味着“有人搞砸了”。但是,让我们考虑一下。与预期行为相比,NPE 作为一个糟糕的指标实际上有什么区别?我认为使用 NPE 作为预期行为的主要区别(和优势)在于它指向的不是发生它的方法,而是指向违反方法合同的调用者。这是更有用的信息。如果我们只是简单地检查 null 并抛出一个不同的异常,我们可能会误以为观察到的行为是预期的错误,而实际上调用者违反了方法的约定。恭喜,您正确地预料到了调用者在调用该方法时可能会搞砸的方式——不管你怎么做
因此,归根结底,人们认为 NPE 是禁忌。从字面上看,人们不会允许它被抛出,因为它会带来一些羞耻感——就好像你不够聪明,因为你没有猜到一个值会在哪里为空。好吧,我有消息告诉你们,你们只是在编写更多无用的代码来做同样的事情。
一些例子:
public void foo(Object o) {
if (o == null) {
throw new IGotchaException("HA! You're an IDIOT! I knew it!!");
}
o.bar();
}
public void foo(Object o) {
o.bar();
}
^ 政治不同。从功能上讲,没有那么多。
public void foo(int a, long b, double c, Object o) {
if (o == null) {
throw new IllegalArgumentException("Oh. Uh. Well, you've passed me null here. I... I'm not sure where to go from here because this object is kind of required for this function to do what it's supposed to do. Soooo... wouldja mind reworking your code a little bit so as to not pass null to this function as a parameter?! That'd be great thanks. Oh by the way, it's cause we deference o 40 lines below here");
}
// ...
o.doSomethingWithA(a);
}
public void foo(int a, long b, double c, Object o) {
// ...
o.doSomethingWithA(a);
// NullPointerException, line 40, e.g. it wasn't OK to pass null for o you lunkhead
}
^ 也许以大量烦人的代码为代价节省了几个 CPU 周期。但是,我们在第二种情况下进行的比较较少。
public void foo(Object a, Object b, Object c, Object d) {
if (a == null) throw IllegalArgumentException("jackass");
if (b == null) throw IllegalArgumentException("jackass");
if (c == null) throw IllegalArgumentException("jackass");
// But d is totally OK!
// ...
c.setSomeValueThatMayBeNull(d);
}
public void foo(Object a, Object b, Object c, Object d) {
// ...
c.setSomeValueThatMayBeNull(d);
// Throws null pointer exception if c is null, but not if d is null. Which is basically the same as above
}
^ 声明中隐含了契约,而不是方法开头的异常情况。没有其他缺点。
public void foo(Object o) {
if (o == null) {
doTheBoogie();
} else {
doTheRobot();
}
}
^ 不好
public void foo(Object o, int b) {
Bar value = o.findSomethingWhichMayExist(b);
if (value == null)
return;
value.doSomething();
}
^ 使用空返回值表示没有值。好的。
人们遇到 NPE 问题的另一个原因是他们不知道如何处理异常。NPE 永远不应该成为阻碍者。适当的行为是捕获 RuntimeException,大概是在调用堆栈中更高(或更低,取决于您如何看待它)的级别,该级别捕获它并在“main”之前报告它。也就是说,假设您正在开发的程序需要更有弹性,并且不能在 NPE 发生时崩溃。
底线:永远不要期望为方法参数传递空值是有效的。并且绝对不要为明确接受 null 并将其视为有效值的方法创建合同。但是,允许发生空指针异常,自然而然地让代码失败,或者在无关紧要时不失败。