Por que isso não está gerando um NullPointerException?

89

Esclarecimento Nead para o seguinte código:

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

Isso será impresso de Bforma que provas samplee referToSampleobjetos se refiram à mesma referência de memória.

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

Isso vai imprimir ABque também prova o mesmo.

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

Obviamente, isso vai lançar NullPointerExceptionporque estou tentando chamar appenduma referência nula.

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

Então, aqui está minha pergunta, por que o último exemplo de código não está sendo lançado, NullPointerExceptionporque o que eu vejo e entendo nos dois primeiros exemplos é se dois objetos se referindo ao mesmo objeto, se alterarmos qualquer valor, ele também refletirá para outro porque ambos estão apontando para mesma referência de memória. Então, por que essa regra não se aplica aqui? Se eu atribuir nulla referToSample, sample também deve ser nulo e deve lançar um NullPointerException, mas não está jogando um, por quê?

comprometer
fonte
31
sampleestá parado sample. Você apenas mudou referToSample.
Dave Newton
25
Votado / marcado com estrela! Pergunta muito básica, mas este é um belo exemplo de como explicar seu problema e fazer bem a pergunta.
Ryan Ransford
15
Um ponto de terminologia em sua pergunta: você continua se referindo a samplee referToSamplecomo objetos, mas eles não são objetos, são variáveis. Uma variável pode conter uma referência a um objeto, mas não é ela própria um objeto. É uma distinção sutil, mas é basicamente a essência da sua confusão.
Daniel Pryden
1
Isso ajuda a pensar em variáveis ​​de objeto apenas como ponteiros. Qualquer operador que actua sobre uma variável ( volatile, final, =, ==...) quando aplicado a um objecto variável afecta o ponteiro , não o objecto que se refere.
MikeFHay
2
@Arpit Eu discordo respeitosamente. Há um grande conceito oculto nesta questão, que é a diferença entre um objeto e uma referência a esse objeto. Na maioria das vezes, não precisamos (nem queremos) estar cientes dessa diferença, e os designers de linguagem trabalham duro para escondê-la de nós. Pense nos argumentos passados ​​por referência em C ++, por exemplo. Portanto, não é surpresa para mim ver iniciantes confusos com toda essa magia!
Gyom

Respostas:

89

nullatribuições não mudam o valor destruindo globalmente esse objeto. Esse tipo de comportamento levaria a bugs difíceis de rastrear e comportamento contra-intuitivo. Eles apenas quebram aquela referência específica .

Para simplificar, digamos que sampleaponta para o endereço 12345. Este provavelmente não é o endereço e é usado apenas para simplificar as coisas aqui. O endereço é normalmente representado com o hexadecimal estranho fornecido Object#hashCode(), mas isso depende da implementação. 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);

A partir das linhas marcadas, See diagramos diagramas dos objetos naquele momento são os seguintes:

Diagrama 1:

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

Diagrama 2:

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

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

O Diagrama 2 mostra que a anulação referToSamplenão quebra a referência de sampleao StringBuilder em 00012345.

1 As considerações do GC tornam isso implausível.

nanofarad
fonte
@commit, se isso responder à sua pergunta, clique no tique abaixo das setas de voto à esquerda do texto acima.
Ray Britton
@RayBritton Adoro como as pessoas presumem que a resposta com maior votação é aquela que necessariamente responde à pergunta. Embora essa contagem seja importante, não é a única métrica; As respostas -1 e -2 podem ser aceitas apenas porque ajudaram mais o OP.
nanofarad de
11
hashCode()não é a mesma coisa que endereço de memória
Kevin Panko
6
A identidade hashCode é normalmente derivada da localização da memória do objeto na primeira vez que o método é chamado. A partir de então, esse hashCode é fixo e lembrado mesmo se o gerenciador de memória decidir mover o objeto para um local de memória diferente.
Holger
3
As classes que substituem hashCodenormalmente o definem para retornar um valor que não tem nada a ver com endereços de memória. Acho que você pode mostrar seu ponto de vista sobre referências a objetos vs. objetos sem mencionar hashCode. Os detalhes de como obter o endereço de memória podem ser deixados para outro dia.
Kevin Panko
62

Inicialmente era como você disse que referToSamplese referia samplecomo mostrado abaixo:

1. Cenário 1:

referToSample refere-se a amostra

2. Cenário 1 (cont.):

referToSample.append ("B")

  • Aqui, como referToSampleestava se referindo a sample, então ele anexou "B" enquanto você escreve

    referToSample.append("B")

A mesma coisa aconteceu no Cenário 2:

Mas, em 3. Cenário 3: como disse a hexafração,

quando você atribui nullpara referToSamplequando ele estava se referindo samplenão mudança de valor em vez disso, apenas quebra a referência a partir de sample, e agora ele aponta a lugar nenhum . como mostrado abaixo:

quando referToSample = null

Agora, como nãoreferToSample aponta para lugar nenhum , embora você referToSample.append("A");não tenha nenhum valor ou referência onde possa anexar A. Então, ele jogaria NullPointerException.

MAS sampleainda é o mesmo que você inicializou com

StringBuilder sample = new StringBuilder(); então ele foi inicializado, então agora ele pode anexar A, e não vai lançar NullPointerException

Parth
fonte
5
Belos diagramas. Ajuda a ilustrar isso.
nanofarad
bom diagrama, mas a nota na parte inferior deve ser 'Agora referToSample não se refere a ""' porque quando você o faz, referToSample = sample;está se referindo à amostra, está apenas copiando o endereço do que é referenciado
Khaled.K
17

Resumindo: você atribui null a uma variável de referência, não a um objeto.

Em um exemplo, você altera o estado de um objeto que é referido por duas variáveis ​​de referência. Quando isso ocorrer, ambas as variáveis ​​de referência refletirão a mudança.

Em outro exemplo, você altera a referência atribuída a uma variável, mas isso não tem efeito sobre o objeto em si e, portanto, a segunda variável, que ainda se refere ao objeto original, não notará qualquer alteração no estado do objeto.


Quanto às suas "regras" específicas:

se dois objetos se referirem ao mesmo objeto, se alterarmos qualquer valor, ele também refletirá para outro, porque ambos estão apontando para a mesma referência de memória.

Novamente, você se refere à alteração do estado de um objeto ao qual ambas as variáveis ​​se referem.

Então, por que essa regra não se aplica aqui? Se eu atribuir null a referToSample, sample também deve ser null e deve lançar nullPointerException, mas não está jogando, por quê?

Novamente, você altera a referência de uma variável que não tem absolutamente nenhum efeito na referência da outra variável.

Essas são duas ações completamente diferentes e resultarão em dois resultados completamente diferentes.

Hovercraft cheio de enguias
fonte
3
@commit: é um conceito básico, mas crítico, que fundamenta todo o Java e que, depois de ver, nunca mais esquecerá.
Hovercraft Full Of Eels
8

Veja este diagrama simples:

diagrama

Quando você chama um método on referToSample, o [your object]é atualizado, por isso afeta sampletambém. Mas quando você diz referToSample = null, então você está simplesmente mudando o que referToSample se refere .

tckmn
fonte
3

Aqui, 'sample' e 'referToSample' fazem referência ao mesmo objeto. Esse é o conceito de ponteiro diferente acessando o mesmo local de memória. Portanto, atribuir uma variável de referência a nulo não destrói o objeto.

   referToSample = null;

significa 'referToSample' apontando apenas para nulo, o objeto permanece o mesmo e outras variáveis ​​de referência estão funcionando bem. Portanto, para 'amostra' que não aponta para nulo e tem um objeto válido

   sample.append("A");

funciona bem. Mas se tentarmos acrescentar null a 'referToSample', ele mostrará NullPointException. Isso é,

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

É por isso que você obteve NullPointerException em seu terceiro trecho de código.

NCA
fonte
0

Sempre que uma nova palavra-chave é usada, ela cria um objeto no heap

1) Amostra StringBuilder = new StringBuilder ();

2) StringBuilder referToSample = amostra;

Em 2) a referência de referSample é criada na mesma amostra de objeto

portanto, referToSample = null; está anulando apenas a referência referSample, não dando efeito à amostra, por isso você não está recebendo a exceção de ponteiro NULL graças à coleta de lixo do Java

nitesh
fonte
0

Simplesmente fácil, Java não tem passagem por referência, apenas passa referência de objeto.

Peerapat A
fonte
2
A semântica de passagem de parâmetros realmente não tem nada a ver com a situação descrita.
Michael Myers