Perché questo non genera un'eccezione NullPointerException?


89

Nead chiarimento per il seguente codice:

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

Questo verrà stampato in Bmodo che le prove samplee gli referToSampleoggetti facciano riferimento allo stesso riferimento di memoria.

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

Questo verrà stampato ABche dimostra anche lo stesso.

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

Ovviamente questo verrà generato NullPointerExceptionperché sto cercando di chiamare appendun riferimento nullo.

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

Quindi ecco la mia domanda, perché l'ultimo esempio di codice non viene lanciato NullPointerExceptionperché quello che vedo e capisco dai primi due esempi è se due oggetti si riferiscono allo stesso oggetto, quindi se cambiamo un valore si rifletterà anche su un altro perché entrambi stanno puntando a stesso riferimento alla memoria. Allora perché questa regola non si applica qui? Se assegno nulla referToSample, anche il campione dovrebbe essere null e dovrebbe generare un'eccezione NullPointerException ma non ne lancia una, perché?


31
sampleè ancora sample. Sei solo cambiato referToSample.
Dave Newton,

25
Votato / aggiunto a Speciali! Domanda molto semplice, ma questo è un bellissimo esempio di come spiegare il tuo problema e porre bene la domanda.
Ryan Ransford

15
Un punto della terminologia nella tua domanda: continui a fare riferimento a samplee referToSamplecome oggetti ma non sono oggetti, sono variabili. Una variabile può contenere un riferimento a un oggetto, ma non è essa stessa un oggetto. È una sottile distinzione, ma fondamentalmente è l'essenza della tua confusione.
Daniel Pryden

1
Aiuta a pensare alle variabili oggetto come semplici puntatori. Qualsiasi operatore che agisce su una variabile ( volatile, final, =, ==...) quando applicato ad una variabile oggetto colpisce il puntatore , non l'oggetto si riferisce.
MikeFHay

2
@ Arpit Rispettosamente non sono d'accordo. C'è un grande concetto nascosto in questa domanda, e cioè, la differenza tra un oggetto e un riferimento a quell'oggetto. Il più delle volte, non abbiamo bisogno (né vogliamo) essere consapevoli di questa differenza e i progettisti del linguaggio lavorano duramente per nasconderla. Basti pensare agli argomenti di passaggio per riferimento in C ++, ad esempio. Quindi non mi sorprende vedere i principianti confusi da tutta quella magia!
Gyom,

Risposte:


89

nullle assegnazioni non cambiano valore distruggendo globalmente quell'oggetto. Questo tipo di comportamento porterebbe a bug difficili da rintracciare e comportamenti controintuitivi. Rompono solo quel riferimento specifico .

Per semplicità, diciamo che samplepunta all'indirizzo 12345. Questo probabilmente non è l'indirizzo e viene utilizzato solo per semplificare le cose qui. L'indirizzo è tipicamente rappresentato con lo strano esadecimale fornito Object#hashCode(), ma questo dipende dall'implementazione. 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);

Dalle linee segnate See diagrami diagrammi degli oggetti in quel momento sono i seguenti:

Diagramma 1:

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

Diagramma 2:

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

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

Il diagramma 2 mostra che l'annullamento referToSamplenon interrompe il riferimento samplea StringBuilder in 00012345.

1 Considerazioni GC rendono questo non plausibile.


@commit, se questo risponde alla tua domanda, fai clic sul segno di spunta sotto le frecce di voto a sinistra del testo sopra.
Ray Britton

@RayBritton Mi piace il modo in cui le persone presumono che la risposta più votata sia quella che risponde necessariamente alla domanda. Sebbene quel conteggio sia importante, non è l'unica metrica; Le risposte -1 e -2 possono essere accettate solo perché hanno aiutato di più l'OP.
nanofarad

11
hashCode()non è la stessa cosa dell'indirizzo di memoria
Kevin Panko,

6
L'identità hashCode viene in genere derivata dalla posizione di memoria dell'oggetto la prima volta che viene chiamato il metodo. Da quel momento in poi quell'hashCode viene corretto e ricordato anche se il gestore della memoria decide di spostare l'oggetto in una posizione di memoria diversa.
Holger

3
Le classi che sovrascrivono in hashCodegenere lo definiscono per restituire un valore che non ha nulla a che fare con gli indirizzi di memoria. Penso che tu possa esprimere il tuo punto sui riferimenti agli oggetti rispetto agli oggetti senza menzionarli hashCode. I punti più fini su come ottenere l'indirizzo di memoria possono essere lasciati per un altro giorno.
Kevin Panko

62

Inizialmente era come hai detto che ti referToSampleriferivi a samplecome mostrato di seguito:

1. Scenario 1:

referToSample si riferisce al campione

2. Scenario 1 (segue):

referToSample.append ("B")

  • Qui come ci referToSamplesi riferiva sample, quindi ha aggiunto "B" mentre scrivi

    referToSample.append("B")

La stessa cosa è successa nello Scenario2:

Ma, nel 3. Scenario 3: come diceva esafrazione,

quando si assegna nulla referToSamplequando si stava riferendo samplenon ha cambiato valore invece si limita a spezzare il riferimento da sample, e ora non punta da nessuna parte . come mostrato di seguito:

quando referToSample = null

Ora, poiché nonreferToSample punta da nessuna parte , quindi mentre tu referToSample.append("A");non avresti alcun valore o riferimento a cui aggiungere A. Quindi, verrebbe lanciato NullPointerException.

MA sampleè sempre lo stesso con cui l'avevi inizializzato

StringBuilder sample = new StringBuilder(); quindi è stato inizializzato, quindi ora può aggiungere A e non verrà lanciato NullPointerException


5
Bei diagrammi. Aiuta a illustrarlo.
nanofarad

bel diagramma, ma la nota in fondo dovrebbe essere 'Now referToSample non si riferisce a ""' perché quando lo fai referToSample = sample;ti riferisci al campione, stai solo copiando l'indirizzo di ciò a cui si fa riferimento
Khaled.K

17

In poche parole: si assegna null a una variabile di riferimento, non a un oggetto.

In un esempio si modifica lo stato di un oggetto a cui fa riferimento due variabili di riferimento. Quando ciò si verifica, entrambe le variabili di riferimento rifletteranno la modifica.

In un altro esempio, si modifica il riferimento assegnato a una variabile, ma ciò non ha alcun effetto sull'oggetto stesso, quindi la seconda variabile, che si riferisce ancora all'oggetto originale, non noterà alcun cambiamento nello stato dell'oggetto.


Quindi, per quanto riguarda le tue "regole" specifiche:

se due oggetti si riferiscono allo stesso oggetto, se modifichiamo un valore, si rifletterà anche su un altro perché entrambi puntano allo stesso riferimento di memoria.

Di nuovo, ti riferisci alla modifica dello stato di un oggetto a cui si riferiscono entrambe le variabili .

Allora perché questa regola non si applica qui? Se assegno null a referToSample, anche sample dovrebbe essere null e dovrebbe generare nullPointerException ma non lancia, perché?

Di nuovo, si modifica il riferimento di una variabile che non ha assolutamente alcun effetto sul riferimento dell'altra variabile.

Queste sono due azioni completamente diverse e porteranno a due risultati completamente diversi.


3
@commit: è un concetto fondamentale ma critico che sta alla base di tutto Java e che una volta che lo vedrai, non lo dimenticherai mai.
Hovercraft Full Of Eels,

8

Vedi questo semplice diagramma:

diagramma

Quando si chiama un metodo su referToSample, quindi [your object]viene aggiornato, in modo che colpisce sampletroppo. Ma quando dici referToSample = null, stai semplicemente cambiando ciò a cui si referToSample riferisce .


3

Qui 'sample' e 'referToSample' fanno riferimento allo stesso oggetto. Questo è il concetto di puntatore diverso che accede alla stessa posizione di memoria. Quindi l'assegnazione di una variabile di riferimento a null non distrugge l'oggetto.

   referToSample = null;

significa 'referToSample' che punta solo a null, l'oggetto rimane lo stesso e altre variabili di riferimento funzionano correttamente. Quindi per 'sample' che non punta a null e ha un oggetto valido

   sample.append("A");

funziona bene. Ma se proviamo ad aggiungere null a 'referToSample', mostrerà NullPointException. Questo è,

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

Ecco perché hai NullPointerException nel tuo terzo snippet di codice.


0

Ogni volta che viene utilizzata una nuova parola chiave, crea un oggetto nell'heap

1) Esempio di StringBuilder = nuovo StringBuilder ();

2) StringBuilder referToSample = campione;

In 2) il riferimento di referSample viene creato sullo stesso oggetto campione

quindi referToSample = null; annulla Solo il riferimento referSample non dà alcun effetto al campione ecco perché non ricevi Eccezione puntatore NULL Grazie alla Garbage Collection di Java


0

Semplicemente facile, Java non deve passare per riferimento, passa solo il riferimento all'oggetto.


2
La semantica di passaggio dei parametri non ha davvero nulla a che fare con la situazione descritta.
Michael Myers
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.