Warum löst dies keine NullPointerException aus?


89

Nead Klarstellung für folgenden Code:

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

Dies wird gedruckt, Bso dass Beweise sampleund referToSampleObjekte auf dieselbe Speicherreferenz verweisen.

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

Dies wird gedruckt AB, was auch das gleiche beweist.

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

Offensichtlich wird dies werfen, NullPointerExceptionweil ich versuche, appendeine Nullreferenz aufzurufen .

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

Hier ist meine Frage, warum das letzte Codebeispiel nicht ausgelöst wird, NullPointerExceptiondenn was ich aus den ersten beiden Beispielen sehe und verstehe, ist, wenn zwei Objekte auf dasselbe Objekt verweisen und wenn wir einen Wert ändern, wird es auch auf andere reflektiert, weil beide auf zeigen gleiche Speicherreferenz. Warum gilt diese Regel hier nicht? Wenn ich nullreferToSample zuweise, sollte sample ebenfalls null sein und eine NullPointerException auslösen, aber es wird keine ausgelöst. Warum?


31
sampleist immer noch sample. Du hast dich nur verändert referToSample.
Dave Newton

25
Upvoted / stared! Sehr grundlegende Frage, aber dies ist ein schönes Beispiel dafür, wie Sie Ihr Problem erklären und die Frage gut stellen können.
Ryan Ransford

15
Ein Punkt der Terminologie in Ihrer Frage: Sie beziehen sich weiterhin auf sampleund referToSampleals Objekte, aber sie sind keine Objekte, sondern Variablen. Eine Variable kann einen Verweis auf ein Objekt enthalten, ist jedoch selbst kein Objekt. Es ist eine subtile Unterscheidung, aber es ist im Grunde die Essenz Ihrer Verwirrung.
Daniel Pryden

1
Es ist hilfreich, sich Objektvariablen nur als Zeiger vorzustellen. Betreiber , die auf einer variable wirkt ( volatile, final, =, ==...) bei der Anwendung auf eine Objektvariablen beeinflußt die Zeiger , nicht das Objekt , das es sich bezieht.
MikeFHay

2
@Arpit Ich bin respektvoll anderer Meinung. In dieser Frage steckt ein großes Konzept, nämlich der Unterschied zwischen einem Objekt und einem Verweis auf dieses Objekt. Meistens müssen (und wollen) wir uns dieses Unterschieds nicht bewusst sein, und Sprachdesigner arbeiten hart daran, ihn vor uns zu verbergen. Denken Sie beispielsweise an Argumente für die Referenzübergabe in C ++. Es überrascht mich also nicht, dass Anfänger von all dieser Magie verwirrt sind!
Gyom

Antworten:


89

nullZuweisungen ändern ihren Wert nicht, indem sie dieses Objekt global zerstören. Diese Art von Verhalten würde zu schwer zu verfolgenden Fehlern und kontraintuitivem Verhalten führen. Sie brechen nur diese spezifische Referenz .

Nehmen wir das der Einfachheit halber an sample auf die Adresse 12345 verwiesen wird. Dies ist wahrscheinlich nicht die Adresse und wird nur verwendet, um die Dinge hier zu . Die Adresse wird normalerweise mit dem seltsamen Hexadezimalwert in angegeben Object#hashCode(), dies ist jedoch implementierungsabhängig. 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);

Aus den markierten Linien ergeben sich folgende See diagramDiagramme der Objekte:

Abbildung 1:

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

Abbildung 2:

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

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

Diagramm 2 zeigt, dass das Annullieren referToSampleden Verweis sampleauf den StringBuilder bei nicht unterbricht 00012345.

1 GC-Überlegungen machen dies unplausibel.


@commit, wenn dies Ihre Frage beantwortet, klicken Sie bitte auf das Häkchen unter den Abstimmungspfeilen links neben dem Text oben.
Ray Britton

@ RayBritton Ich liebe es, wie die Leute annehmen, dass die Antwort mit der höchsten Stimme diejenige ist, die die Frage unbedingt beantwortet. Obwohl diese Anzahl wichtig ist, ist sie nicht die einzige Metrik. -1- und -2-Antworten können akzeptiert werden, nur weil sie dem OP am meisten geholfen haben.
Nanofarad

11
hashCode()ist nicht dasselbe wie Speicheradresse
Kevin Panko

6
Der Identitäts-Hashcode wird normalerweise beim ersten Aufruf der Methode vom Speicherort des Objekts abgeleitet. Von da an ist dieser Hashcode fest und wird gespeichert, selbst wenn der Speichermanager beschließt, das Objekt an einen anderen Speicherort zu verschieben.
Holger

3
Klassen, die überschreiben, hashCodedefinieren es normalerweise, um einen Wert zurückzugeben, der nichts mit Speicheradressen zu tun hat. Ich denke, Sie können Ihren Standpunkt zu Objektreferenzen im Vergleich zu Objekten äußern, ohne dies zu erwähnen hashCode. Die Feinheiten, wie man die Speicheradresse erhält, können für einen weiteren Tag belassen werden.
Kevin Panko

62

Am Anfang war es, wie Sie sagten referToSample, samplewie unten gezeigt:

1. Szenario 1:

referToSample bezieht sich auf sample

2. Szenario 1 (Forts.):

referToSample.append ("B")

  • Hier, wie referToSamplees sich bezog, samplewurde beim Schreiben "B" angehängt

    referToSample.append("B")

Das Gleiche geschah in Szenario 2:

Aber in 3. Szenario 3: Wie Hexafraktion sagte,

Wenn Sie zuweisen null, referToSamplewann es referenziert hat , hat es den Wertsample nicht geändert , sondern nur die Referenz von gebrochensample , und jetzt zeigt es nirgendwo hin . Wie nachfolgend dargestellt:

wenn referToSample = null

Nun, als referToSamplePunkte nirgendwo , also während Sie referToSample.append("A");keine Werte oder Referenzen haben würden, an die es A anhängen kann. Also würde es werfenNullPointerException .

ABER sample ist immer noch dasselbe, mit dem Sie es initialisiert haben

StringBuilder sample = new StringBuilder(); so wurde es initialisiert, so dass es jetzt A anhängen kann und nicht werfen wird NullPointerException


5
Schöne Diagramme. Hilft es zu veranschaulichen.
Nanofarad

schönes Diagramm, aber der Hinweis unten sollte lauten: "Jetzt bezieht sich referToSample nicht auf" ", denn wenn Sie referToSample = sample;sich auf sample beziehen, kopieren Sie nur die Adresse dessen, worauf verwiesen wird
Khaled.K

17

Kurz gesagt: Sie weisen einer Referenzvariablen null zu, nicht einem Objekt.

In einem Beispiel ändern Sie den Status eines Objekts, auf das zwei Referenzvariablen verweisen. In diesem Fall spiegeln beide Referenzvariablen die Änderung wider.

In einem anderen Beispiel ändern Sie die einer Variablen zugewiesene Referenz, dies hat jedoch keine Auswirkungen auf das Objekt selbst. Daher bemerkt die zweite Variable, die sich immer noch auf das ursprüngliche Objekt bezieht, keine Änderung des Objektstatus.


Zu Ihren spezifischen "Regeln":

Wenn sich zwei Objekte auf dasselbe Objekt beziehen und wenn wir einen Wert ändern, wird dies auch für andere Objekte angezeigt, da beide auf dieselbe Speicherreferenz zeigen.

Sie beziehen sich erneut auf das Ändern des Status des einen Objekts, auf das sich beide Variablen beziehen.

Warum gilt diese Regel hier nicht? Wenn ich referToSample null zuweise, sollte sample ebenfalls null sein und nullPointerException auslösen, aber es wird nicht ausgelöst. Warum?

Auch hier ändern Sie die Referenz einer Variablen, was absolut keine Auswirkung auf die Referenz der anderen Variablen hat.

Dies sind zwei völlig unterschiedliche Aktionen, die zu zwei völlig unterschiedlichen Ergebnissen führen.


3
@commit: Es ist ein grundlegendes, aber kritisches Konzept, das ganz Java zugrunde liegt und das Sie nie vergessen werden, wenn Sie es einmal gesehen haben.
Luftkissenfahrzeug voller Aale

8

Siehe dieses einfache Diagramm:

Diagramm

Wenn Sie einen Anruf Methode auf referToSample, dann [your object]wird aktualisiert, so dass es wirkt sich sampleauch. Aber wenn Sie sagen referToSample = null, dann ändern Sie einfach, worauf sich referToSample bezieht .


3

Hier verweisen 'sample' und 'referToSample' auf dasselbe Objekt. Dies ist das Konzept eines unterschiedlichen Zeigers, der auf denselben Speicherort zugreift. Das Zuweisen einer Referenzvariablen zu null zerstört das Objekt also nicht.

   referToSample = null;

bedeutet, dass 'referToSample' nur auf null zeigt, das Objekt gleich bleibt und andere Referenzvariablen einwandfrei funktionieren. Also für 'sample', das nicht auf null zeigt und ein gültiges Objekt hat

   sample.append("A");

funktioniert gut. Wenn wir jedoch versuchen, null an 'referToSample' anzuhängen, wird NullPointException angezeigt. Das ist,

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

Aus diesem Grund haben Sie in Ihrem dritten Code-Snippet die NullPointerException erhalten.


0

Immer wenn ein neues Schlüsselwort verwendet wird, wird ein Objekt auf dem Heap erstellt

1) StringBuilder sample = new StringBuilder ();

2) StringBuilder referToSample = sample;

In 2) wird die Referenz von referSample für dasselbe Objektmuster erstellt

also referToSample = null; ist Nulling Nur die referSample-Referenz hat keine Auswirkung auf das Sample, weshalb Sie dank Javas Garbage Collection keine NULL-Zeigerausnahme erhalten


0

Nur einfach, Java hat keine Referenzübergabe. Es übergibt nur eine Objektreferenz.


2
Die Semantik der Parameterübergabe hat eigentlich nichts mit der beschriebenen Situation zu tun.
Michael Myers
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.