Pregunta ¿Java es "pass-by-reference" o "pass-by-value"?


Siempre pensé que Java era pass-by-reference.

Sin embargo, he visto un par de publicaciones en el blog (por ejemplo, este blog) que afirman que no lo es.

No creo entender la distinción que están haciendo.

¿Cuál es la explicación?


5482


origen


Respuestas:


Java es siempre pass-by-value. Desafortunadamente, decidieron llamar a la ubicación de un objeto una "referencia". Cuando pasamos el valor de un objeto, estamos pasando el referencia lo. Esto es confuso para principiantes.

Dice así:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    // we pass the object to foo
    foo(aDog);
    // aDog variable is still pointing to the "Max" dog when foo(...) returns
    aDog.getName().equals("Max"); // true
    aDog.getName().equals("Fifi"); // false 
}

public static void foo(Dog d) {
    d.getName().equals("Max"); // true
    // change d inside of foo() to point to a new Dog instance "Fifi"
    d = new Dog("Fifi");
    d.getName().equals("Fifi"); // true
}

En el ejemplo anterior aDog.getName() todavía regresará "Max". El valor aDog dentro main no se cambia en la función foo con el Dog  "Fifi" como la referencia del objeto se pasa por valor. Si se pasó por referencia, entonces el aDog.getName() en main volvería "Fifi" después de la llamada a foo.

Igualmente:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    foo(aDog);
    // when foo(...) returns, the name of the dog has been changed to "Fifi"
    aDog.getName().equals("Fifi"); // true
}

public static void foo(Dog d) {
    d.getName().equals("Max"); // true
    // this changes the name of d to be "Fifi"
    d.setName("Fifi");
}

En el ejemplo anterior, Fifi es el nombre del perro después de llamar a foo(aDog) porque el nombre del objeto se estableció dentro de foo(...). Cualquier operación que foo realiza en d son tales que, para todos los fines prácticos, se realizan en aDog sí mismo (excepto cuando d se cambia para señalar a un diferente Dog ejemplo como d = new Dog("Boxer"))


4734



Acabo de notar que te mencionaron mi articulo.

Java Spec dice que todo en Java es pass-by-value. No existe tal cosa como "pass-by-reference" en Java.

La clave para entender esto es que algo así como

Dog myDog;

es no un perro; en realidad es un puntero a un perro

Lo que eso significa, es cuando tienes

Dog myDog = new Dog("Rover");
foo(myDog);

esencialmente estás pasando el dirección de lo creado Dog objetar a la foo método.

(Digo esencialmente porque los punteros de Java no son direcciones directas, pero es más fácil pensar en ellos de esa manera)

Supongamos que Dog el objeto reside en la dirección de memoria 42. Esto significa que pasamos 42 al método.

si el Método se definió como

public void foo(Dog someDog) {
    someDog.setName("Max");     // AAA
    someDog = new Dog("Fifi");  // BBB
    someDog.setName("Rowlf");   // CCC
}

echemos un vistazo a lo que está sucediendo.

  • El parámetro someDog se establece en el valor 42
  • en la línea "AAA"
    • someDog es seguido al Dog apunta a (el Dog objeto en la dirección 42)
    • ese Dog (el de la dirección 42) se le pide que cambie su nombre a Max
  • en la línea "BBB"
    • un nuevo Dog es creado. Digamos que está en la dirección 74
    • asignamos el parámetro someDog a 74
  • en la línea "CCC"
    • SomeDog se sigue al Dog apunta a (el Dog objeto en la dirección 74)
    • ese Dog (el de la dirección 74) se le pide que cambie su nombre a Rowlf
  • entonces, volvemos

Ahora pensemos en lo que ocurre fuera del método:

Hizo myDog ¿cambio?

Ahí está la clave.

Teniendo en cuenta que myDog es un puntero, y no un real Dog, la respuesta es no. myDog todavía tiene el valor 42; todavía está apuntando al original Dog(pero tenga en cuenta que debido a la línea "AAA", su nombre ahora es "Máx." - sigue siendo el mismo perro; myDogEl valor de este no ha cambiado.

Es perfectamente válido para seguir una dirección y cambiar lo que está al final; eso no cambia la variable, sin embargo.

Java funciona exactamente como C. Puede asignar un puntero, pasar el puntero a un método, seguir el puntero en el método y cambiar los datos que se señalaron. Sin embargo, no puede cambiar dónde apunta ese puntero.

En C ++, Ada, Pascal y otros lenguajes que soportan pass-by-reference, en realidad puede cambiar la variable que se pasó.

Si Java tenía semántica de paso por referencia, el foo método que hemos definido anteriormente habría cambiado donde myDog estaba señalando cuando asignó someDog en línea BBB.

Piense en los parámetros de referencia como alias para la variable pasada. Cuando se asigna ese alias, también lo es la variable que se pasó.


2637



Java siempre pasa argumentos por valor NO por referencia.


Déjame explicar esto a través de un ejemplo:

public class Main{
     public static void main(String[] args){
          Foo f = new Foo("f");
          changeReference(f); // It won't change the reference!
          modifyReference(f); // It will modify the object that the reference variable "f" refers to!
     }
     public static void changeReference(Foo a){
          Foo b = new Foo("b");
          a = b;
     }
     public static void modifyReference(Foo c){
          c.setAttribute("c");
     }
}

Explicaré esto en pasos:

  1. Declarando una referencia llamada f de tipo Foo y asignarlo a un nuevo objeto de tipo Foo con un atributo "f".

    Foo f = new Foo("f");
    

    enter image description here

  2. Desde el lado del método, una referencia de tipo Foo con un nombre a se declara y se asigna inicialmente a null.

    public static void changeReference(Foo a)
    

    enter image description here

  3. Como llamas al método changeReference, la referencia a se asignará al objeto que se pasa como un argumento.

    changeReference(f);
    

    enter image description here

  4. Declarando una referencia llamada b de tipo Foo y asignarlo a un nuevo objeto de tipo Foo con un atributo "b".

    Foo b = new Foo("b");
    

    enter image description here

  5. a = b está reasignando la referencia a NO f al objeto cuyo atributo es "b".

    enter image description here


  6. Cuando llamas modifyReference(Foo c) método, una referencia c se crea y se asigna al objeto con atributo "f".

    enter image description here

  7. c.setAttribute("c"); cambiará el atributo del objeto que hace referencia c lo señala, y es el mismo objeto que referencia f apunta a eso.

    enter image description here

Espero que comprendan ahora cómo funcionan los objetos con argumentos en Java :)


1388



Esto te dará una idea de cómo funciona realmente Java hasta el punto de que en tu próxima discusión sobre Java pasando por referencia o pasando por valor, solo sonreirás :-)

Paso uno borre de su mente esa palabra que comienza con 'p' "_ _ _ _ _ _ _", especialmente si proviene de otros lenguajes de programación. Java y 'p' no se pueden escribir en el mismo libro, foro o incluso txt.

El segundo paso recuerda que cuando pasas un Objeto a un método estás pasando la referencia del Objeto y no el Objeto mismo.

  • Estudiante: Maestro, ¿esto significa que Java es de paso por referencia?
  • Dominar: Grasshopper, No.

Ahora piense en lo que hace / es una referencia / variable de un objeto:

  1. Una variable contiene los bits que le dicen a la JVM cómo llegar al Objeto referenciado en la memoria (Heap).
  2. Al pasar argumentos a un método NO estás pasando la variable de referencia, sino una copia de los bits en la variable de referencia. Algo como esto: 3bad086a. 3bad086a representa una forma de llegar al objeto pasado.
  3. Así que solo está pasando 3bad086a que es el valor de la referencia.
  4. Está pasando el valor de la referencia y no la referencia en sí (y no el objeto).
  5. Este valor es en realidad COPIADO y dado al método.

A continuación, (no intente compilar / ejecutar esto ...):

1. Person person;
2. person = new Person("Tom");
3. changeName(person);
4.
5. //I didn't use Person person below as an argument to be nice
6. static void changeName(Person anotherReferenceToTheSamePersonObject) {
7.     anotherReferenceToTheSamePersonObject.setName("Jerry");
8. }

¿Lo que pasa?

  • La variable persona se crea en la línea n. ° 1 y es nulo al principio.
  • Se crea un nuevo objeto de persona en la línea 2, se almacena en la memoria y la variable persona se le da la referencia al objeto Persona. Es decir, su dirección. Digamos 3bad086a.
  • La variable persona manteniendo la dirección del Objeto se pasa a la función en la línea # 3.
  • En la línea # 4 puedes escuchar el sonido del silencio
  • Verifique el comentario en la línea # 5
  • Una variable local de método -anotherReferenceToTheSamePersonObject- se crea y luego viene la magia en la línea # 6:
    • La variable / referencia persona se copia poco a poco y se pasa a anotherReferenceToTheSamePersonObject dentro de la función.
    • No se crean nuevas instancias de Persona.
    • Ambos "persona"y"anotherReferenceToTheSamePersonObject"mantener el mismo valor de 3bad086a.
    • No intente esto, pero la persona == anotherReferenceToTheSamePersonObject sería verdadera.
    • Ambas variables tienen COPIAS IDENTICAS de la referencia y ambas se refieren al mismo objeto de persona, el MISMO objeto en el montón y NO A UNA COPIA.

Una imagen vale mas que mil palabras:

Pass by Value

Tenga en cuenta que las flechas anotherReferenceToTheSamePersonObject se dirigen hacia el objeto y no hacia la persona variable.

Si no lo obtuviste, confía en mí y recuerda que es mejor decir que Java es pasar por valor. Bien, pasar por valor de referencia. Oh, bueno, aún mejor es pass-by-copy-of-the-value! ;)

Ahora siéntete libre de odiarme pero ten en cuenta que dado esto no hay diferencia entre pasar tipos de datos primitivos y objetos cuando se habla de argumentos de método.

¡Siempre pasa una copia de los bits del valor de la referencia!

  • Si se trata de un tipo de datos primitivo, estos bits contendrán el valor del tipo de datos primitivo en sí.
  • Si es un Objeto, los bits contendrán el valor de la dirección que le dice a la JVM cómo llegar al Objeto.

Java es paso por valor porque dentro de un método puede modificar el objeto al que hace referencia tanto como lo desee, pero no importa cuánto lo intente, nunca podrá modificar la variable pasada que seguirá haciendo referencia (no p _ _ _ _ _ _ _) el mismo Objeto sin importar qué!


La función changeName anterior nunca podrá modificar el contenido real (los valores de bit) de la referencia aprobada. En otras palabras changeName no puede hacer que Persona persona se refiera a otro Object.


Por supuesto, puedes cortarlo y solo decir eso Java es paso por valor!


651



Java siempre pasa por valor, sin excepciones, nunca.

Entonces, ¿cómo es que alguien puede estar confundido por esto, y cree que Java pasa por referencia, o cree que tienen un ejemplo de Java que actúa como paso por referencia? El punto clave es que Java Nunca proporciona acceso directo a los valores de objetos mismos, en alguna circunstancias. El único acceso a los objetos es a través de un referencia a ese objeto Porque los objetos de Java son siempre se accede a través de una referencia, en lugar de hacerlo directamente, es común hablar de campos y variables y argumentos del método como siendo objetoscuando pedanticamente son solo referencias a objetos. La confusión proviene de este cambio (estrictamente hablando, incorrecto) en la nomenclatura.

Entonces, cuando llamas a un método

  • Para argumentos primitivos (int, long, etc.), el valor de paso es el valor real del primitivo (por ejemplo, 3).
  • Para objetos, el valor de paso por valor es el valor de la referencia al objeto.

Entonces si tienes doSomething(foo) y public void doSomething(Foo foo) { .. } los dos Foos han copiado referencias que apuntan a los mismos objetos.

Naturalmente, al pasar de valor, una referencia a un objeto se parece mucho (y es indistinguible en la práctica) de pasar un objeto por referencia.


561



Java pasa referencias por valor.

Entonces no puedes cambiar la referencia que se pasa.


278



Tengo ganas de discutir sobre "pasar por referencia vs pasar por valor" no es muy útil.

Si dice: "Java pasa de largo (lo que sea) (referencia / valor)", en cualquier caso, no proporciona una respuesta completa. Aquí hay información adicional que ayudará a comprender lo que está sucediendo en la memoria.

Curso intensivo en pila / pila antes de llegar a la implementación de Java: Los valores van y vienen de la pila de una manera agradable y ordenada, como una pila de platos en una cafetería. La memoria en el montón (también conocida como memoria dinámica) es fortuita y desorganizada. La JVM simplemente encuentra espacio donde puede y lo libera, ya que las variables que lo usan ya no son necesarias.

Bueno. Primero, los primitivos locales van a la pila. Entonces este código:

int x = 3;
float y = 101.1f;
boolean amIAwesome = true;

resultados en esto:

primitives on the stack

Cuando declaras e instancias un objeto. El objeto real va en el montón. ¿Qué pasa en la pila? La dirección del objeto en el montón. Los programadores de C ++ llamarían a esto un puntero, pero algunos desarrolladores de Java están en contra de la palabra "puntero". Lo que sea. Solo sepa que la dirección del objeto va a la pila.

Al igual que:

int problems = 99;
String name = "Jay-Z";

a b*7ch aint one!

Una matriz es un objeto, por lo que va en el montón también. ¿Y qué hay de los objetos en la matriz? Consiguen su propio espacio de montón, y la dirección de cada objeto va dentro de la matriz.

JButton[] marxBros = new JButton[3];
marxBros[0] = new JButton("Groucho");
marxBros[1] = new JButton("Zeppo");
marxBros[2] = new JButton("Harpo");

marx brothers

Entonces, ¿qué pasa cuando llamas a un método? Si pasas en un objeto, lo que en realidad estás pasando es la dirección del objeto. Algunos podrían decir el "valor" de la dirección, y algunos dicen que es solo una referencia al objeto. Esta es la génesis de la guerra santa entre los defensores de "referencia" y "valor". Lo que usted llama no es tan importante como que usted entienda que lo que se transfiere es la dirección del objeto.

private static void shout(String name){
    System.out.println("There goes " + name + "!");
}

public static void main(String[] args){
    String hisName = "John J. Jingleheimerschmitz";
    String myName = hisName;
    shout(myName);
}

Se crea una cadena y se asigna espacio para ella en el montón, y la dirección de la cadena se almacena en la pila y se le da el identificador. hisName, dado que la dirección de la segunda cadena es la misma que la primera, no se crea una nueva cadena y no se asigna un nuevo espacio de pila, sino que se crea un nuevo identificador en la pila. Entonces llamamos shout(): se crea un nuevo marco de pila y un nuevo identificador, name se crea y se le asigna la dirección de la cadena ya existente.

la da di da da da da

Entonces, ¿valor, referencia? Usted dice "papa".


198



Solo para mostrar el contraste, compara lo siguiente C ++ y Java fragmentos de código:

En C ++: Nota: código incorrecto - ¡pérdidas de memoria!  Pero demuestra el punto.

void cppMethod(int val, int &ref, Dog obj, Dog &objRef, Dog *objPtr, Dog *&objPtrRef)
{
    val = 7; // Modifies the copy
    ref = 7; // Modifies the original variable
    obj.SetName("obj"); // Modifies the copy of Dog passed
    objRef.SetName("objRef"); // Modifies the original Dog passed
    objPtr->SetName("objPtr"); // Modifies the original Dog pointed to 
                               // by the copy of the pointer passed.
    objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer, 
                                   // leaving the original object alone.
    objPtrRef->SetName("objRefPtr"); // Modifies the original Dog pointed to 
                                    // by the original pointer passed. 
    objPtrRef = new Dog("newObjPtrRef"); // Modifies the original pointer passed
}

int main()
{
    int a = 0;
    int b = 0;
    Dog d0 = Dog("d0");
    Dog d1 = Dog("d1");
    Dog *d2 = new Dog("d2");
    Dog *d3 = new Dog("d3");
    cppMethod(a, b, d0, d1, d2, d3);
    // a is still set to 0
    // b is now set to 7
    // d0 still have name "d0"
    // d1 now has name "objRef"
    // d2 now has name "objPtr"
    // d3 now has name "newObjPtrRef"
}

En Java,

public static void javaMethod(int val, Dog objPtr)
{
   val = 7; // Modifies the copy
   objPtr.SetName("objPtr") // Modifies the original Dog pointed to 
                            // by the copy of the pointer passed.
   objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer, 
                                  // leaving the original object alone.
}

public static void main()
{
    int a = 0;
    Dog d0 = new Dog("d0");
    javaMethod(a, d0);
    // a is still set to 0
    // d0 now has name "objPtr"
}

Java solo tiene los dos tipos de aprobación: por valor para los tipos incorporados y por valor del puntero para los tipos de objeto.


161



Java pasa referencias a objetos por valor.


145