Pourquoi cela ne lance-t-il pas une NullPointerException?

89

Nead clarification pour le code suivant:

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

Cela s'imprimera de Bsorte que les preuves sampleet les referToSampleobjets se réfèrent à la même référence mémoire.

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

Cela imprimera ABqui prouve également la même chose.

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

Évidemment, cela jettera NullPointerExceptionparce que j'essaye d'appeler appendune référence nulle.

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

Donc, voici ma question, pourquoi le dernier exemple de code ne lance-t-il pas NullPointerExceptionparce que ce que je vois et comprends à partir des deux premiers exemples, c'est si deux objets faisant référence au même objet, si nous modifions une valeur, il reflétera également l'autre car les deux pointent vers même référence mémoire. Alors pourquoi cette règle ne s'applique-t-elle pas ici? Si nullj'assigne à referToSample, sample doit également être null et il doit lancer une NullPointerException mais il n'en lance pas une, pourquoi?

commettre
la source
31
sampleest toujours sample. Vous avez seulement changé referToSample.
Dave Newton
25
Vote positif / suivi! Question très basique, mais c'est un bel exemple pour expliquer votre problème et bien poser la question.
Ryan Ransford
15
Un point de terminologie dans votre question: vous continuez à faire référence à sampleet en referToSampletant qu'objets mais ce ne sont pas des objets, ce sont des variables. Une variable peut contenir une référence à un objet, mais elle n'est pas elle-même un objet. C'est une distinction subtile, mais c'est essentiellement l'essence de votre confusion.
Daniel Pryden
1
Il est utile de considérer les variables objet comme de simples pointeurs. Tout opérateur qui agit sur une variable ( volatile, final, =, ==...) , lorsqu'elle est appliquée à une variable d'objet affecte le pointeur , et non l'objet , il se réfère.
MikeFHay
2
@Arpit Je suis respectueusement en désaccord. Il y a un grand concept caché dans cette question, et c'est la différence entre un objet et une référence à cet objet. La plupart du temps, nous n'avons pas besoin (ni ne voulons) être conscients de cette différence, et les concepteurs de langage travaillent dur pour nous la cacher. Pensez simplement aux arguments de passage par référence en C ++, par exemple. Ce n'est donc pas étonnant pour moi de voir des débutants déconcertés par toute cette magie!
Gyom le

Réponses:

89

nullles affectations ne changent pas de valeur en détruisant globalement cet objet. Ce type de comportement conduirait à des bogues difficiles à suivre et à un comportement contre-intuitif. Ils ne cassent que cette référence spécifique .

Pour simplifier, disons que cela samplepointe vers l'adresse 12345. Ce n'est probablement pas l'adresse, et n'est utilisé que pour simplifier les choses ici. L'adresse est généralement représentée avec l'étrange hexadécimal donné dans Object#hashCode(), mais cela dépend de l'implémentation. 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);

À partir des lignes marquées, See diagramles schémas des objets à ce moment-là sont les suivants:

Diagramme 1:

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

Schéma 2:

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

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

Le diagramme 2 montre que l'annulation referToSamplene rompt pas la référence de sampleà StringBuilder at 00012345.

1 Les considérations du CG rendent cela invraisemblable.

nanofarad
la source
@commit, si cela répond à votre question, veuillez cliquer sur la coche sous les flèches de vote à gauche du texte ci-dessus.
Ray Britton
@RayBritton J'aime la façon dont les gens supposent que la réponse la plus votée est celle qui répond nécessairement à la question. Bien que ce décompte soit important, ce n'est pas la seule métrique; Les réponses -1 et -2 peuvent être acceptées simplement parce qu'elles ont le plus aidé le PO.
nanofarad
11
hashCode()n'est pas la même chose que l'adresse mémoire
Kevin Panko
6
Le hashCode d'identité est généralement dérivé de l'emplacement mémoire de l'objet la première fois que la méthode est appelée. À partir de là, ce hashCode est fixé et mémorisé même si le gestionnaire de mémoire décide de déplacer l'objet vers un emplacement mémoire différent.
Holger
3
Les classes qui remplacent le hashCodedéfinissent généralement pour renvoyer une valeur qui n'a rien à voir avec les adresses mémoire. Je pense que vous pouvez faire votre point sur les références d'objets par rapport aux objets sans mentionner hashCode. Les détails de la façon d'obtenir l'adresse mémoire peuvent être laissés pour un autre jour.
Kevin Panko
62

Initialement , il était comme vous avez dit referToSamplese référait à samplecomme indiqué ci - dessous:

1. Scénario1:

referToSample fait référence à un échantillon

2. Scénario1 (suite):

referToSample.append ("B")

  • Ici comme cela referToSamplefaisait référence sample, donc il a ajouté "B" pendant que vous écrivez

    referToSample.append("B")

La même chose s'est produite dans le scénario 2:

Mais, dans 3. Scénario3: comme l'a dit l'hexafraction,

lorsque vous attribuez nullà referToSamplequand il faisait référence , sampleil n'a pas changé la valeur au lieu de cela casse juste la référence de sample, et il fait maintenant nulle part . comme indiqué ci-dessous:

quand referToSample = null

Maintenant, comme ne referToSamplepointe nulle part , alors que vous, referToSample.append("A");il n'aurait aucune valeur ou référence où il peut ajouter A. Donc, il lancerait NullPointerException.

MAIS sampleest toujours le même que vous l'aviez initialisé avec

StringBuilder sample = new StringBuilder(); donc il a été initialisé, donc maintenant il peut ajouter A, et ne lancera pas NullPointerException

Parth
la source
5
Beaux diagrammes. Aide à l'illustrer.
nanofarad
joli diagramme, mais la note en bas devrait être 'Now referToSample ne fait pas référence à ""' parce que lorsque vous faites referToSample = sample;référence à sample, vous copiez simplement l'adresse de ce qui est référencé
Khaled.K
17

En un mot: vous affectez null à une variable de référence, pas à un objet.

Dans un exemple, vous modifiez l' état d'un objet auquel se réfèrent deux variables de référence. Lorsque cela se produit, les deux variables de référence refléteront le changement.

Dans un autre exemple, vous modifiez la référence affectée à une variable, mais cela n'a aucun effet sur l'objet lui-même, et ainsi la deuxième variable, qui fait toujours référence à l'objet d'origine, ne remarquera aucun changement d'état de l'objet.


En ce qui concerne vos "règles" spécifiques:

si deux objets font référence au même objet, alors si nous modifions une valeur, cela se reflétera également sur l'autre car les deux pointent vers la même référence de mémoire.

Encore une fois, vous faites référence à la modification de l' état du seul objet auquel les deux variables font référence.

Alors pourquoi cette règle ne s'applique-t-elle pas ici? Si j'attribue null à referToSample, sample doit également être nul et lancer une exception nullPointerException mais il ne lance pas, pourquoi?

Encore une fois, vous modifiez la référence d'une variable qui n'a absolument aucun effet sur la référence de l'autre variable.

Ce sont deux actions complètement différentes et aboutiront à deux résultats complètement différents.

Aéroglisseur plein d'anguilles
la source
3
@commit: c'est un concept basique mais critique qui sous-tend tout Java et qu'une fois que vous voyez, vous n'oublierez jamais.
Aéroglisseur plein d'anguilles
8

Voir ce schéma simple:

diagramme

Lorsque vous appelez une méthode sur referToSample, puis [your object]est mis à jour, il affecte sampleaussi. Mais quand vous dites referToSample = null, vous changez simplement de quoi il referToSample s'agit .

tckmn
la source
3

Ici, «sample» et «referToSample» font référence au même objet. C'est le concept de pointeur différent accédant au même emplacement mémoire. Ainsi, affecter une variable de référence à null ne détruit pas l'objet.

   referToSample = null;

signifie 'referToSample' ne pointant que sur null, l'objet reste le même et les autres variables de référence fonctionnent correctement. Donc pour 'sample' qui ne pointe pas sur null et qui a un objet valide

   sample.append("A");

fonctionne très bien. Mais si nous essayons d'ajouter null à 'referToSample', cela affichera NullPointException. C'est,

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

C'est pourquoi vous avez NullPointerException dans votre troisième extrait de code.

NCA
la source
0

Chaque fois qu'un nouveau mot-clé est utilisé, il crée un objet au niveau du tas

1) exemple StringBuilder = nouveau StringBuilder ();

2) StringBuilder referToSample = exemple;

En 2) la référence de referSample est créée sur le même objet échantillon

donc referToSample = null; Nulling Seule la référence referSample ne donne aucun effet à l'échantillon, c'est pourquoi vous n'obtenez pas d'exception de pointeur NULL grâce à la récupération de place de Java

nitesh
la source
0

Simplement facile, Java n'a pas de passe par référence, il passe simplement la référence d'objet.

Peerapat A
la source
2
La sémantique de passage de paramètres n'a vraiment rien à voir avec la situation décrite.
Michael Myers