¿Por qué esto no arroja una NullPointerException?

89

Nead aclaración para el siguiente código:

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

Esto se imprimirá Bpara que las pruebas sampley los referToSampleobjetos se refieran a la misma referencia de memoria.

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

Esto imprimirá ABque también prueba lo mismo.

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

Obviamente, esto arrojará NullPointerExceptionporque estoy tratando de llamar appenda una referencia nula.

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

Entonces, aquí está mi pregunta, ¿por qué la última muestra de código no arroja? NullPointerExceptionPorque lo que veo y entiendo de los primeros dos ejemplos es si dos objetos se refieren al mismo objeto, entonces, si cambiamos cualquier valor, también se reflejará en otro porque ambos apuntan a misma referencia de memoria. Entonces, ¿por qué esa regla no se aplica aquí? Si asigno nulla referToSample, la muestra también debería ser nula y debería arrojar una NullPointerException pero no arroja una, ¿por qué?

cometer
fuente
31
sampleestá quieto sample. Solo cambiaste referToSample.
Dave Newton
25
¡Votado / destacado! Pregunta muy básica, pero este es un hermoso ejemplo de cómo explicar su problema y hacer bien la pregunta.
Ryan Ransford
15
Un punto de terminología en su pregunta: sigue refiriéndose a sampley referToSamplecomo objetos, pero no son objetos, son variables. Una variable puede contener una referencia a un objeto, pero no es en sí misma un objeto. Es una distinción sutil, pero es básicamente la esencia de tu confusión.
Daniel Pryden
1
Ayuda pensar en las variables de objeto como simples punteros. Cualquier operador que actúa sobre una variable ( volatile, final, =, ==...) cuando se aplica a una variable de objeto afecta al puntero , no el objeto que se refiere.
MikeFHay
2
@Arpit Respetuosamente no estoy de acuerdo. Hay un gran concepto oculto en esta pregunta, y es la diferencia entre un objeto y una referencia a ese objeto. La mayoría de las veces, no necesitamos (ni queremos) ser conscientes de esta diferencia, y los diseñadores de lenguajes se esfuerzan por ocultárnosla. Solo piense en argumentos de paso por referencia en C ++, por ejemplo. ¡Así que no me sorprende ver a los principiantes confundidos por toda esa magia!
Gyom

Respuestas:

89

nulllas asignaciones no cambian el valor al destruir globalmente ese objeto. Ese tipo de comportamiento conduciría a errores difíciles de rastrear y comportamientos contrarios a la intuición. Solo rompen esa referencia específica .

Para simplificar, digamos que sampleapunta a la dirección 12345. Probablemente esta no sea la dirección, y solo se usa para simplificar las cosas aquí. La dirección generalmente se representa con el extraño hexadecimal dado Object#hashCode(), pero esto depende de la implementación. 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);

De las líneas marcadas See diagram los diagramas de los objetos en ese momento son los siguientes:

Diagrama 1:

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

Diagrama 2:

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

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

El diagrama 2 muestra que la anulación referToSampleno rompe la referencia desample al StringBuilder en 00012345.

1 consideraciones de GC hacen que esto sea inverosímil.

nanofaradio
fuente
@commit, si esto responde a su pregunta, haga clic en la marca debajo de las flechas de voto a la izquierda del texto de arriba.
Ray Britton
@RayBritton Me encanta cómo la gente asume que la respuesta más votada es la que necesariamente responde a la pregunta. Si bien ese recuento es importante, no es la única métrica; Las respuestas -1 y -2 pueden aceptarse solo porque ayudaron más al OP.
nanofarad
11
hashCode()no es lo mismo que la dirección de memoria
Kevin Panko
6
La identidad hashCode se deriva típicamente de la ubicación de la memoria del Objeto la primera vez que se llama al método. A partir de ese momento, ese hashCode se fija y se recuerda incluso si el administrador de memoria decide mover el objeto a una ubicación de memoria diferente.
Holger
3
Las clases que anulan hashCodenormalmente lo definen para devolver un valor que no tiene nada que ver con las direcciones de memoria. Creo que puede exponer su punto sobre referencias a objetos frente a objetos sin mencionarlos hashCode. Los puntos más finos de cómo obtener la dirección de memoria se pueden dejar para otro día.
Kevin Panko
62

Inicialmente fue como dijiste que referToSamplese refería a lo sampleque se muestra a continuación:

1. Escenario 1:

referToSample se refiere a la muestra

2. Escenario 1 (cont.):

referToSample.append ("B")

  • Aquí, como referToSamplese refería sample, se agregó "B" mientras escribe

    referToSample.append("B")

Lo mismo sucedió en el escenario 2:

Pero, en 3. Escenario 3: como dijo la hexafracción,

cuando se asigna nulla referToSamplecuando se refería sampleNo cambió el valor en vez de eso me rompe la referencia a sample, y ahora apunta a ninguna parte . Como se muestra abajo:

cuando referToSample = null

Ahora, como noreferToSample apunta a ninguna parte , mientras que usted referToSample.append("A");no tendría ningún valor o referencia donde pueda agregar A. Entonces, arrojaríaNullPointerException .

PERO samplesigue siendo el mismo que lo había inicializado con

StringBuilder sample = new StringBuilder(); por lo que se ha inicializado, por lo que ahora puede agregar A, y no arrojará NullPointerException

Parth
fuente
5
Buenos diagramas. Ayuda a ilustrarlo.
nanofarad
bonito diagrama, pero la nota en la parte inferior debe ser 'Ahora referToSample no se refiere a ""' porque cuando lo hace referToSample = sample;, se refiere a la muestra, simplemente está copiando la dirección de lo que se hace referencia
Khaled.K
17

En pocas palabras: asigna nulo a una variable de referencia, no a un objeto.

En un ejemplo cambias el estado de un objeto al que hacen referencia dos variables de referencia. Cuando esto ocurre, ambas variables de referencia reflejarán el cambio.

En otro ejemplo, cambia la referencia asignada a una variable, pero esto no tiene ningún efecto en el objeto en sí, por lo que la segunda variable, que aún se refiere al objeto original, no notará ningún cambio en el estado del objeto.


En cuanto a sus "reglas" específicas:

si dos objetos se refieren al mismo objeto, si cambiamos cualquier valor, también se reflejará en otro porque ambos apuntan a la misma referencia de memoria.

Nuevamente, se refiere a cambiar el estado de un objeto al que se refieren ambas variables .

Entonces, ¿por qué esa regla no se aplica aquí? Si asigno nulo a referToSample, la muestra también debería ser nula y debería arrojar nullPointerException pero no arroja, ¿por qué?

Nuevamente, cambia la referencia de una variable que no tiene absolutamente ningún efecto sobre la referencia de la otra variable.

Estas son dos acciones completamente diferentes y darán como resultado dos resultados completamente diferentes.

Aerodeslizador lleno de anguilas
fuente
3
@commit: es un concepto básico pero crítico que subyace a todo Java y que una vez que lo vea, nunca lo olvidará.
Aerodeslizador lleno de anguilas
8

Vea este diagrama simple:

diagrama

Cuando se llama a un método en referToSample, a continuación, [your object]se actualiza, por lo que afecta sampletambién. Pero cuando dices referToSample = null, simplemente estás cambiando a qué se referToSample refiere .

tckmn
fuente
3

Aquí 'muestra' y 'referToSample' hacen referencia al mismo objeto. Ese es el concepto de puntero diferente que accede a la misma ubicación de memoria. Entonces, asignar una variable de referencia a nulo no destruye el objeto.

   referToSample = null;

significa que 'referToSample' solo apunta a nulo, el objeto sigue siendo el mismo y otras variables de referencia funcionan bien. Entonces, para 'muestra' que no apunta a nulo y tiene un objeto válido

   sample.append("A");

funciona bien. Pero si intentamos agregar un valor nulo a 'referToSample', mostrará NullPointException. Es decir,

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

Es por eso que obtuvo NullPointerException en su tercer fragmento de código.

NCA
fuente
0

Siempre que se usa una nueva palabra clave, se crea un objeto en el montón

1) Ejemplo de StringBuilder = new StringBuilder ();

2) StringBuilder referToSample = muestra;

En 2) la referencia de referSample se crea en la misma muestra de objeto

por lo tanto, referToSample = null; está anulando solo la referencia referSample que no da ningún efecto a la muestra, por eso no obtiene la excepción de puntero NULL gracias a la recolección de basura de Java

nitesh
fuente
0

Simplemente fácil, Java no tiene paso por referencia, solo pasa la referencia del objeto.

Peerapat A
fuente
2
La semántica de paso de parámetros no tiene nada que ver con la situación descrita.
Michael Myers