<p>两个点放在一起起初可能会有点尴尬:</p> <pre><code>f = 1..__truediv__ # or 1..__div__ for python 2 </code></pre> 为什么要显式抛出 NullPointerException 而不是让它自然发生呢?

但这和写作是一样的:

f = 1.0.__truediv__ # or 1.0.__div__ for python 2

在阅读 JDK 源代码时,我发现作者通常会检查参数是否为 null,然后手动抛出新的 NullPointerException ()。

因为 float文字可以用三种形式书写:

normal_float = 1.0
short_float = 1.  # == 1.0
prefixed_float = .1  # == 0.1
他们为什么要这么做?我认为没有必要这样做,因为它在调用任何方法时都会抛出新的 NullPointerException ()。(例如,下面是 HashMap 的一些源代码:)

public V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
if (remappingFunction == null)
throw new NullPointerException();
Node<K,V> e; V oldValue;
int hash = hash(key);
if ((e = getNode(hash, key)) != null &&
(oldValue = e.value) != null) {
V v = remappingFunction.apply(key, oldValue);
if (v != null) {
e.value = v;
afterNodeAccess(e);
return v;
}
else
removeNode(hash, key, null, false, true);
}
return null;
}
14597 次浏览
< ? super K,? super V,? 扩展 V > reappingfunction){

它是故意保护进一步的损害,或进入不一致的状态。

If (reappingfunction = = null) 抛出新的 NullPointerException () ;

它是为了清晰、一致,并防止执行额外的、不必要的工作。

节点 < K,V > e;

考虑一下,如果在方法的顶部没有监护子句,会发生什么情况。它总是调用 hash(key)getNode(hash, key),即使在抛出 NPE 之前 null已经被传入 remappingFunction

Int hash = hash (key) ; 如果((e = getNode (hash,key)) ! = null & &

更糟糕的是,如果 if条件是 false,那么我们使用 else分支,它根本不使用 remappingFunction,这意味着当传递 null时,方法并不总是抛出 NPE; 是否这样做取决于映射的状态。

这两种情况都很糟糕。如果 null对于 remappingFunction来说不是一个有效的值,那么无论在调用时对象的内部状态如何,这个方法都应该始终抛出一个异常,而且这样做的时候不应该做不必要的工作,这些工作是毫无意义的,因为它只会抛出异常。最后,有一个干净、清晰的代码的好原则,那就是让守卫站在前面,这样任何审查源代码的人都能很容易地看到它会这样做。

(oldValue = e.value) ! = null){ 应用(key,oldValue) ;

即使当前每个代码分支都抛出了异常,将来对代码的修订也有可能改变这一点。在开始时执行检查可以确保它一定会被执行。

如果(v! = null){

这样,一旦出现错误,您就会得到异常,而不是在以后使用映射时不明白为什么会发生异常。

值 = v;

我想到了一些原因,其中有几个是密切相关的:

接达后(e) ;

快速失败: 如果要失败,最好尽早失败。这使得问题更接近于它们的源头,使得它们更容易识别和恢复。它还避免了在注定要失败的代码上浪费 CPU 周期。

返回; } 别的

意图: 抛出异常显式地向维护人员表明错误是有意存在的,并且作者知道后果。

RemoveNode (hash、 key、 null、 false、 true) ; }

一致性: 如果允许错误自然发生,那么它可能不会发生在每个场景中。例如,如果没有找到映射,就不会使用 remappingFunction,也不会抛出异常。预先验证输入允许更确定的行为和更清晰的 文件

返回空; } R”> 文档 .

稳定性: 代码随着时间的推移而发展。自然遇到异常的代码在经过一段时间的重构之后,可能会停止这样做,或者在不同的情况下停止这样做。明确地抛出它可以减少行为在不经意间发生变化的可能性。

除了@shmosel 的精彩回答所列出的理由外... ..。

性能: 显式抛出 NPE 而不是让 JVM 这样做可能(在某些 JVM 上)有性能优势。

这个问题已经得到了充分的回答(比如 @ Paul Rooney的回答) ,但是也可以验证这些回答的正确性。

它取决于 Java 解释器和 JIT 编译器检测空指针解引用的策略。一种策略是不测试 null,而是捕获指令试图访问地址0时发生的 SIGSEGV。在引用始终有效的情况下,这是最快的方法,但在 NPE 情况下,它是 很贵

让我回顾一下现有的答案: ..不是一个单一的语法元素!

在代码中对 null进行显式测试可以避免在 NPE 频繁出现的场景中出现 SIGSEGV 性能问题。

您可以检查源代码如何是 “标记”。这些标记表示如何解释代码:

>>> from tokenize import tokenize
>>> from io import BytesIO


>>> s = "1..__truediv__"
>>> list(tokenize(BytesIO(s.encode('utf-8')).readline))
[...
TokenInfo(type=2 (NUMBER), string='1.', start=(1, 0), end=(1, 2), line='1..__truediv__'),
TokenInfo(type=53 (OP), string='.', start=(1, 2), end=(1, 3), line='1..__truediv__'),
TokenInfo(type=1 (NAME), string='__truediv__', start=(1, 3), end=(1, 14), line='1..__truediv__'),
...]

(我怀疑这在现代 JVM 中是否是一个有价值的微优化,但在过去是可能的。)


兼容性: 异常中没有消息的可能原因是为了与 JVM 本身抛出的 NPE 兼容。在兼容的 Java 实现中,JVM 抛出的 NPE 具有 null消息。(Android Java 不同。)

因此字符串 1.被解释为 number,第二个 .是 OP (操作符,在本例中是“ get 属性”操作符) ,而 __truediv__是方法名。所以这只是访问浮点 1.0__truediv__方法。

正在访问浮点 1.0__truediv__方法。

查看生成的字节码的另一种方式是 to装配 it。这实际上显示了执行某些代码时执行的指令:

>>> import dis


>>> def f():
...     return 1..__truediv__


>>> dis.dis(f)
4           0 LOAD_CONST               1 (1.0)
3 LOAD_ATTR                0 (__truediv__)
6 RETURN_VALUE

查看生成的字节码的另一种方式是 to装配 it。这实际上显示了执行某些代码时执行的指令:

>>> import dis


>>> def f():
...     return 1..__truediv__


>>> dis.dis(f)
4           0 LOAD_CONST               1 (1.0)
3 LOAD_ATTR                0 (__truediv__)
6 RETURN_VALUE

基本上是一样的,它加载常量 1.0的属性 __truediv__


基本上是一样的,它加载常量 1.0的属性 __truediv__


关于你的问题

关于你的问题

如何在更复杂的语句中使用它(如果可能的话) ?

如何在更复杂的语句中使用它(如果可能的话) ?

即使您可能永远不应该编写这样的代码,仅仅是因为不清楚代码在做什么。所以请不要在更复杂的语句中使用它。我甚至可以说,你不应该在如此“简单”的语句中使用它,至少你应该使用括号来分隔指令:

f = (1.).__truediv__

即使您可能永远不应该编写这样的代码,仅仅是因为不清楚代码在做什么。所以请不要在更复杂的语句中使用它。我甚至可以说,你不应该在如此“简单”的语句中使用它,至少你应该使用括号来分隔指令:

f = (1.).__truediv__

这肯定会更容易理解,但大致是这样的:

from functools import partial
from operator import truediv
f = partial(truediv, 1.0)

这肯定会更容易理解,但大致是这样的:

from functools import partial
from operator import truediv
f = partial(truediv, 1.0)

会更好!

会更好!

使用 partial的方法还保留了 Python 的数据模型(1..__truediv__方法没有!) ,可以通过下面这个小片段来演示:

>>> f1 = 1..__truediv__
>>> f2 = partial(truediv, 1.)


>>> f2(1+2j)  # reciprocal of complex number - works
(0.2-0.4j)
>>> f2('a')   # reciprocal of string should raise an exception
TypeError: unsupported operand type(s) for /: 'float' and 'str'


>>> f1(1+2j)  # reciprocal of complex number - works but gives an unexpected result
NotImplemented
>>> f1('a')   # reciprocal of string should raise an exception but it doesn't
NotImplemented

使用 partial的方法还保留了 Python 的数据模型(1..__truediv__方法没有!) ,可以通过下面这个小片段来演示:

>>> f1 = 1..__truediv__
>>> f2 = partial(truediv, 1.)


>>> f2(1+2j)  # reciprocal of complex number - works
(0.2-0.4j)
>>> f2('a')   # reciprocal of string should raise an exception
TypeError: unsupported operand type(s) for /: 'float' and 'str'


>>> f1(1+2j)  # reciprocal of complex number - works but gives an unexpected result
NotImplemented
>>> f1('a')   # reciprocal of string should raise an exception but it doesn't
NotImplemented

这是因为 1. / (1+2j)不由 float.__truediv__计算,而是由 complex.__rtruediv__-operator.truediv确保在正常操作返回 NotImplemented时调用反向操作,但是在直接操作 __truediv__时没有这些后备操作。这种“预期行为”的缺失是您(通常)不应该直接使用魔法方法的主要原因。

什么是 f = 1..__truediv__

f是浮点数的绑定特殊方法,值为1,

1.0 / x

除了其他人已经指出的,在这里传统的作用是值得注意的。例如,在 C # 中,在这种情况下也有显式引发异常的相同约定,但是它是一个特定的 ArgumentNullException,这个约定更具体一些。(C # 约定是,NullReferenceException 一直都是代表某种类型的 bug ——非常简单,它不应该发生在产品代码中; 当然,ArgumentNullException通常也是这样,但它可能是一个类似于“你不知道如何正确使用库”的 bug)。

在 Python 3中,调用:

(1.0).__truediv__(x)

证据:

class Float(float):
def __truediv__(self, other):
print('__truediv__ called')
return super(Float, self).__truediv__(other)

以及:

>>> one = Float(1)
>>> one/2
__truediv__ called
0.5

所以,基本上,在 C # NullReferenceException中,意味着程序实际上尝试使用它,而 ArgumentNullException意味着它认识到值是错误的,它甚至没有尝试使用它。其含义实际上可能是不同的(取决于具体情况) ,因为 ArgumentNullException意味着所讨论的方法还没有副作用(因为它没有满足方法的前提条件)。

如果我们这样做:

f = one.__truediv__

顺便说一句,如果您要引发类似 ArgumentNullExceptionIllegalArgumentException的异常,那就是进行检查的部分要点: 您希望获得与“通常”获得的异常不同的异常。

我们保留绑定到该绑定方法的名称

>>> f(2)
__truediv__ called
0.5
>>> f(3)
__truediv__ called
0.3333333333333333

无论采用哪种方式,显式引发异常都会强化显式说明方法的前置条件和预期参数的良好做法,从而使代码更容易阅读、使用和维护。如果您没有显式地检查 null,我不知道这是否是因为您认为没有人会传递 null参数,您正在计算它以抛出异常,或者您只是忘记检查它。

本身就是一个 SyntaxError,但是它开始对 float 的实例进行点状查找:

>>> 1..__truediv__
<method-wrapper '__truediv__' of float object at 0x0D1C7BF0>

如果我们在一个紧凑的循环中进行点查找,这可以节省一点时间。

解析抽象语法树(AST)

没有其他人提到这个 -这现在是浮点上的一个 “绑定法”1.0:

>>> f = 1..__truediv__
>>> f
<method-wrapper '__truediv__' of float object at 0x127F3CD8>
>>> f(2)
0.5
>>> f(3)
0.33333333333333331

我们可以看到,为表达式解析 AST 告诉我们,我们正在获取浮点数 1.0上的 __truediv__属性:

>>> import ast
>>> ast.dump(ast.parse('1..__truediv__').body[0])
"Expr(value=Attribute(value=Num(n=1.0), attr='__truediv__', ctx=Load()))"

我们可以更好地完成同样的功能:

>>> def divide_one_by(x):
...     return 1.0/x
...
>>> divide_one_by(2)
0.5
>>> divide_one_by(3)
0.33333333333333331

你可以得到相同的结果函数:

f = float(1).__truediv__

表演

或者

f = (1.0).__truediv__

divide_one_by函数的缺点是它需要另一个 Python 堆栈帧,这使得它比绑定方法稍慢:

>>> def f_1():
...     for x in range(1, 11):
...         f(x)
...
>>> def f_2():
...     for x in range(1, 11):
...         divide_one_by(x)
...
>>> timeit.repeat(f_1)
[2.5495760687176485, 2.5585621018805469, 2.5411816588331888]
>>> timeit.repeat(f_2)
[3.479687248616699, 3.46196088706062, 3.473726342237768]

推论

当然,如果你只是使用简单的文字,那就更快了:

>>> def f_3():
...     for x in range(1, 11):
...         1.0/x
...
>>> timeit.repeat(f_3)
[2.1224895628296281, 2.1219930218637728, 2.1280188256941983]

我们也可以通过扣除来达到这个目的。

它将一个看似不稳定的错误条件转变为一个明确的契约违背: 函数具有正确工作的一些先决条件,因此它提前检查它们,强制它们得到满足。

我们开始吧。

其效果是,当从 computeIfPresent()中获取异常时,不必调试它。一旦您看到异常来自前置条件检查,您就知道您使用非法参数调用了该函数。如果检查不存在,则需要排除 computeIfPresent()本身内部存在某些导致引发异常的 bug 的可能性。

1本身就是 int:

>>> 1
1
>>> type(1)
<type 'int'>

显然,抛出通用 NullPointerException是一个非常糟糕的选择,因为它本身并不意味着违反契约。IllegalArgumentException会是一个更好的选择。


在浮点数之后加一个句点:

>>> 1.
1.0
>>> type(1.)
<type 'float'>

旁注:

下一个点本身是一个 SyntaxError,但它开始对 float 的实例进行点状查找:

>>> 1..__truediv__
<method-wrapper '__truediv__' of float object at 0x0D1C7BF0>
我不知道 Java 是否允许这样做(我对此表示怀疑) ,但是 C/C + + 程序员在这种情况下使用 assert(),这对于调试来说明显更好: 它告诉程序立即崩溃,并且如果所提供的条件计算为 false,则尽可能使程序崩溃。所以,如果你逃跑

void MyClass_foo(MyClass* me, int (*someFunction)(int)) {
assert(me);
assert(someFunction);


...
}

没有其他人提到这个 -这现在是浮点上的一个 “绑定法”1.0:

>>> f = 1..__truediv__
>>> f
<method-wrapper '__truediv__' of float object at 0x127F3CD8>
>>> f(2)
0.5
>>> f(3)
0.33333333333333331

我们可以更好地完成同样的功能:

>>> def divide_one_by(x):
...     return 1.0/x
...
>>> divide_one_by(2)
0.5
>>> divide_one_by(3)
0.33333333333333331

表演

divide_one_by函数的缺点是它需要另一个 Python 堆栈帧,这使得它比绑定方法稍慢:

>>> def f_1():
...     for x in range(1, 11):
...         f(x)
...
>>> def f_2():
...     for x in range(1, 11):
...         divide_one_by(x)
...
>>> timeit.repeat(f_1)
[2.5495760687176485, 2.5585621018805469, 2.5411816588331888]
>>> timeit.repeat(f_2)
[3.479687248616699, 3.46196088706062, 3.473726342237768]

当然,如果你只是使用简单的文字,那就更快了:

>>> def f_3():
...     for x in range(1, 11):
...         1.0/x
...
>>> timeit.repeat(f_3)
[2.1224895628296281, 2.1219930218637728, 2.1280188256941983]

在调试器下,有东西将 NULL传递到任何一个参数中,程序就会在指定哪个参数是 NULL的那一行停止,你就可以随时检查整个调用堆栈的所有局部变量。

这是因为 没有的自然发生是可能的。让我们看看这样的代码:

bool isUserAMoron(User user) {
Connection c = UnstableDatabase.getConnection();
if (user.name == "Moron") {
// In this case we don't need to connect to DB
return true;
} else {
return c.makeMoronishCheck(user.id);
}
}

我尝试从角4发送一个 POST 请求到我的 Laravel 后端。

(当然在这个例子中有很多关于代码质量的问题,对不起,我懒得去想象完美的例子)

My LoginService 有以下方法:

login(email: string, password: string) {
return this.http.post(`http://10.0.1.19/login`, { email, password })
}

c将不会实际使用的情况下,即使 c == null是可能的,也不会抛出 NullPointerException

我在 LoginComponent 中订阅了这个方法:

.subscribe(
(response: any) => {
console.log(response)
location.reload()
},
(error: any) => {
console.log(error)
})

在更复杂的情况下,追查这样的案件变得非常不容易。这就是为什么一般检查像 if (c == null) throw new NullPointerException()是更好的。

这是我的 Laravel 后端方法:

...


if($this->auth->attempt(['email' => $email, 'password' => $password], true)) {
return response('Success', 200);
}


return response('Unauthorized', 401);
或者 block,给我这个消息:

解析 http://10.0.1.19/api/login时 Http 失败

我的 Chrome 开发工具显示我的请求成功了,有200个状态码。但是我的角度编码触发 error模块,给我这样的信息:

解析 http://10.0.1.19/api/login时 Http 失败

如果我从我的后端返回一个空数组,它工作... 所以 Angular 试图解析我的响应为 JSON?我怎么才能解除这个?

有点短,而且没什么信息。

您可以使用 responseType指定返回的数据是 没有 JSON。

堆栈跟踪将提供大量信息,但是为了让用户知道什么是 null,他们必须挖掘代码并查看确切的行。

在您的示例中,可以使用 responseType字符串值 text:

return this.http.post(
'http://10.0.1.19/login',
{email, password},
{responseType: 'text'})

现在假设 NPE 被包装并通过网络发送,例如作为 Web 服务错误中的消息,可能在不同的部门甚至组织之间。最坏的情况是,没人能搞清楚 null代表什么。

responseType的完整选择清单如下:

    链式方法调用会让您不断猜测

  • json(默认值)
  • text
  • 异常只会告诉您异常发生在哪一行。请考虑以下行:

    repository.getService(someObject.someMethod());
    
  • arraybuffer
  • 如果您得到一个 NPE 并且它指向这一行,那么 repositorysomeObject中的哪一个是空的?

  • blob

相反,在获取这些变量时检查它们将至少指向一行,在这一行中,它们可能是唯一被处理的变量。如前所述,如果您的错误消息包含变量名或类似的名称,那就更好了。

处理大量输入时的错误应提供识别信息

假设您的程序正在处理一个包含数千行的输入文件,突然出现一个 NullPointerException。你看看这个地方,发现有些输入不正确... 什么输入?您将需要关于行号、列甚至整个行文本的更多信息,以了解该文件中的哪一行需要修复。