为什么这不引发NullPointerException?


89

需要对以下代码进行澄清:

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,但不引发一个,为什么?


31
sample还是sample。你只是改变了referToSample
戴夫·牛顿2013年

25
已投票/加注星标!非常基本的问题,但这是一个很好的例子,可以解释您的问题很好地提出问题。
瑞安·兰斯福德2013年

15
问题中的一个术语是:您不断引用samplereferToSample作为对象,但它们不是对象,而是变量。变量可以保存对对象的引用,但它本身不是对象。这是一个微妙的区别,但这基本上是您困惑的本质。
Daniel Pryden 2013年

1
它有助于将对象变量视为指针。作用于一个变量的任何操作符(volatilefinal===...)当施加到一个对象变量影响指针,而不是它是指该对象。
MikeFHay 2013年

2
@Arpit,我谨不同意。这个问题中隐藏着一个大概念,那就是一个对象与对该对象的引用之间的区别。大多数时候,我们不需要(也不希望)意识到这种差异,并且语言设计师会努力将其隐藏起来。例如,仅考虑C ++中的按引用传递参数。因此,让初学者对所有这些魔术感到困惑,这对我来说并不奇怪!
Gyom 2013年

Answers:


89

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对StringBuilder 的引用00012345

1 GC的考虑使其难以置信。


@commit,如果这回答了您的问题,请单击上方文本左侧投票箭头下方的对勾。
雷·布里顿

@RayBritton我喜欢人们如何认为投票率最高的答案是必然回答问题的答案。尽管该计数很重要,但它并不是唯一的度量标准。-1和-2答案之所以能够被接受,是因为它们对OP的帮助最大。
nanofarad 2013年

11
hashCode()不是一回事内存地址
凯文Panko

6
标识hashCode通常是在第一次调用该方法时从对象的内存位置派生的。从那时起,即使内存管理器决定将对象移动到其他内存位置,也将固定并记住该hashCode。
Holger 2013年

3
覆盖的类hashCode通常将其定义为返回与内存地址无关的值。我认为您可以在不提及的情况下就对象引用与对象发表意见 hashCode。有关如何获取内存地址的更详细的说明可以再保留一天。
凯文·潘科

62

最初,它是如你所说referToSample指的是sample如下图所示:

1.方案1:

ReferToSample引用样本

2.方案1(续):

referToSample.append(“ B”)

  • 此处referToSample指的是sample,因此在您编写时它会附加“ B”

    referToSample.append("B")

Scenario2中发生了同样的事情

但是,在 3. Scenario3中:正如hexafraction所说,

当你将nullreferToSample当它是指sample它没有变化,而不是它只是打破了参考sample,而现在它指向无处。如下所示:

当ReferToSample = null时

现在,作为referToSample无门,所以当你referToSample.append("A");它不会有地方可以追加A.因此,任何值或引用时,它会抛出NullPointerException

但是sample仍然与您使用初始化时相同

StringBuilder sample = new StringBuilder(); 所以它已经被初始化,所以现在它可以附加A,并且不会抛出 NullPointerException


5
漂亮的图表。有助于说明这一点。
nanofarad 2013年

漂亮的图表,但底部的注释应该是“现在referToSample不再引用””,因为当您referToSample = sample;引用样本时,您只是在复制引用的地址
Khaled.K

17

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

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

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


关于您的特定“规则”:

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

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

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

同样,您更改一个变量的引用,这绝对不会影响另一个变量的引用

这是两个完全不同的动作,将导致两个完全不同的结果。


3
@commit:这是一个基本但至关重要的概念,它是所有Java的基础,一旦您看到,您将永远不会忘记。
气垫船充满鳗鱼

8

看这个简单的图:

图

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


3

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

   referToSample = null;

表示“ referToSample”仅指向null,对象保持不变,而其他引用变量正常运行。因此,对于不指向null且具有有效对象的“样本”

   sample.append("A");

工作良好。但是,如果我们尝试将null附加到'referToSample',它将显示NullPointException。那是,

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

这就是为什么在第三个代码段中出现NullPointerException的原因。


0

每当新的关键字时,都会在堆上创建一个对象

1)StringBuilder示例= new StringBuilder();

2) StringBuilder referToSample =样本;

2)在同一对象样本上创建referSample的Reference

因此ReferToSample = null; 是Nulling仅referSample参考对示例无效,这就是为什么您没有得到NULL指针异常的原因感谢Java的Garbage Collection


0

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


2
参数传递语义实际上与所描述的情况没有任何关系。
迈克尔·迈尔斯
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.