为啥这不抛出 NullPointerException?

Posted

技术标签:

【中文标题】为啥这不抛出 NullPointerException?【英文标题】:Why is this not throwing a NullPointerException?为什么这不抛出 NullPointerException? 【发布时间】:2013-09-09 07:14:29 【问题描述】:

需要澄清以下代码:

StringBuilder sample = new StringBuilder();
StringBuilder referToSample = sample;
referToSample.append("B");
System.out.println(sample);

这将打印B,从而证明samplereferToSample 对象引用相同的内存引用。

StringBuilder sample = new StringBuilder();
StringBuilder referToSample = sample;
sample.append("A");
referToSample.append("B");
System.out.println(referToSample);

这将打印出AB,这也证明了这一点。

StringBuilder sample = new StringBuilder();
StringBuilder referToSample = sample;
referToSample = null;
referToSample.append("A");
System.out.println(sample);

显然这会抛出NullPointerException,因为我试图在空引用上调用append

StringBuilder sample = new StringBuilder();
StringBuilder referToSample = sample;
referToSample = null;
sample.append("A");
System.out.println(sample);

所以这是我的问题,为什么最后一个代码示例没有抛出NullPointerException,因为我从前两个示例中看到并理解的是,如果两个对象引用同一个对象,那么如果我们更改任何值,那么它也会反映到其他因为两者都指向相同的内存引用。那么为什么这条规则在这里不适用呢?如果我将null 分配给referToSample,那么sample 也应该为null,它应该抛出一个NullPointerException 但它没有抛出一个,为什么?

【问题讨论】:

sample 仍然是 sample。你只改变了referToSample 点赞/加星标!非常基本的问题,但这是一个很好的例子,可以解释您的问题很好地提出问题。 您的问题中有一点术语:您一直将samplereferToSample 称为objects,但它们不是对象,它们是变量。变量可以保存对对象的引用,但它本身不是对象。这是一个微妙的区别,但它基本上是你困惑的本质。 将对象变量视为指针会有所帮助。任何作用于变量(volatilefinal===...)的运算符在应用于对象变量时会影响 指针,而不是它所引用的对象. @Arpit 我不同意。这个问题隐藏了一个很大的概念,那就是 object 和对该对象的 reference 之间的区别。大多数时候,我们不需要(也不想)意识到这种差异,并且语言设计者努力向我们隐藏它。例如,想想 C++ 中的传递引用参数。所以看到初学者被所有的魔法弄糊涂,我并不奇怪! 【参考方案1】:

null 赋值不会通过全局销毁该对象来更改 。这种行为会导致难以跟踪的错误和违反直觉的行为。他们只会破坏特定的引用

为简单起见,假设sample 指向地址12345。这可能不是地址,在这里只是为了简单起见。 地址通常Object#hashCode()中给出的奇怪的十六进制表示,但这取决于实现。1

StringBuilder sample = new StringBuilder(); //sample refers to 
//StringBuilder at 12345 

StringBuilder referToSample = sample; //referToSample refers to 
//the same StringBuilder at 12345 
//SEE DIAGRAM 1

referToSample = null; //referToSample NOW refers to 00000, 
//so accessing it will throw a NPE. 
//The other reference is not affected.
//SEE DIAGRAM 2

sample.append("A"); //sample STILL refers to the same StringBuilder at 12345 
System.out.println(sample);

从标有See diagram的行来看,当时的对象图如下:

图表 1:

[StringBuilder sample]    -----------------> [java.lang.StringBuilder@00012345]
                                                      ↑
[StringBuilder referToSample] ------------------------/

图表 2:

[StringBuilder sample]    -----------------> [java.lang.StringBuilder@00012345]

[StringBuilder referToSample] ---->> [null pointer]

图 2 显示取消 referToSample 不会破坏 sample 对位于 00012345 的 StringBuilder 的引用。

1GC 考虑使这不可信。

【讨论】:

@commit,如果这回答了您的问题,请点击上方文本左侧投票箭头下方的勾号。 @RayBritton 我喜欢人们认为投票最高的答案是必然回答问题的答案。虽然这个计数很重要,但它不是唯一的指标; -1 和 -2 的答案可以被接受,因为它们对 OP 的帮助最大。 hashCode() 与内存地址相同 标识 hashCode 通常是在第一次调用该方法时从 Object 的内存位置派生的。从那时起,即使内存管理器决定将对象移动到不同的内存位置,hashCode 也会被固定并记住。 覆盖hashCode 的类通常将其定义为返回与内存地址无关的值。我认为您可以在不提及hashCode 的情况下就对象引用与对象提出您的观点。如何获取内存地址的细节可以留到另一天。【参考方案2】:

最初就像你说的referToSample指的是sample,如下所示:

1.场景1:

2.场景 1(续):

这里referToSample指的是sample,所以它附加了“B” 在你写的时候

referToSample.append("B")

场景 2 中也发生了同样的事情:

但是,在 3.场景3:正如hexafraction所说,

当您将null 分配给referToSample 时,当它引用sample 时,它并没有改变,而是破坏了来自sample引用,现在它指向无处。如下图:

现在,referToSample 指向 nowhere,所以当你 referToSample.append("A"); 时,它不会有任何值或引用可以附加 A。所以,它会抛出 NullPointerException

但是 sample 仍然和你用

初始化的一样

StringBuilder sample = new StringBuilder();所以已经初始化了,所以现在可以追加A了,不会抛出NullPointerException

【讨论】:

漂亮的图表。有助于说明它。 不错的图表,但底部的注释应该是'Now referToSample 不引用“”'因为当你做referToSample = sample;时你指的是样本,你只是在复制地址引用了什么【参考方案3】:

简而言之:您将 null 分配给引用变量,而不是对象。

在一个示例中,您更改了由两个引用变量引用的对象的状态。发生这种情况时,两个参考变量都会反映这种变化。

在另一个示例中,您更改分配给一个变量的引用,但这对对象本身没有影响,因此仍然引用原始对象的第二个变量不会注意到对象状态的任何变化。


至于你的具体“规则”:

如果两个对象引用同一个对象,那么如果我们更改任何值,那么它也会反映到另一个对象,因为它们都指向同一个内存引用。

同样,您指的是更改两个变量所指的一个对象的状态

那么为什么这条规则在这里不适用?如果我将 null 分配给 referToSample 那么 sample 也应该是 null 并且它应该抛出 nullPointerException 但它没有抛出,为什么?

同样,您更改了一个变量的 reference,这对另一个变量的 reference 绝对没有影响

这是两个完全不同的动作,会产生两个完全不同的结果。

【讨论】:

@commit:这是一个基本但关键的概念,它是所有 Java 的基础,一旦你看到,你就永远不会忘记。【参考方案4】:

看这个简单的图表:

当您在referToSample 上调用方法 时,[your object] 会更新,因此它也会影响sample。但是当你说referToSample = null 时,你只是在改变referToSample 所指的内容。

【讨论】:

【参考方案5】:

这里的 'sample' 和 'referToSample' 引用的是同一个对象。这就是不同指针访问同一个内存位置的概念。因此,将一个引用变量分配给 null 不会破坏对象。

   referToSample = null;

表示 'referToSample' 仅指向 null ,对象保持不变,其他参考变量工作正常。因此,对于不指向 null 并且具有有效对象的“样本”

   sample.append("A");

工作正常。但是如果我们尝试将 null 附加到“referToSample”,它将显示 NullPointException。 也就是说,

   referToSample .append("A");-------> NullPointerException

这就是您在第三个代码 sn-p 中出现 NullPointerException 的原因。

【讨论】:

【参考方案6】:

每当使用 new 关键字时,它都会在堆中创建一个对象

1)StringBuilder 示例 = new StringBuilder();

2)StringBuilder referToSample = sample;

在 2) 中,referSample 的引用是在同一个对象样本上创建的

因此 referToSample = null; 是 Nulling 只有referSample 参考对样本没有影响 这就是为什么你没有得到 NULL 指针异常 感谢 Java 的垃圾收集器

【讨论】:

【参考方案7】:

很简单,Java没有引用传递,它只是传递对象引用。

【讨论】:

参数传递语义与所描述的情况没有任何关系。

以上是关于为啥这不抛出 NullPointerException?的主要内容,如果未能解决你的问题,请参考以下文章

捕获异常并重新抛出它,但这不是异常

StringUtils.isEmpty和StringUtils.isBlank用法

为啥这不起作用?爪哇

为啥这不会超载?

为啥这不起作用? [姜戈]

为啥有时这不起作用?