¿Java es "paso por referencia" o "paso por valor"?


6582

Siempre pensé que Java era paso por referencia .

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?


706
Creo que gran parte de la confusión sobre este tema tiene que ver con el hecho de que diferentes personas tienen diferentes definiciones del término "referencia". Las personas que provienen de un fondo de C ++ asumen que "referencia" debe significar lo que significaba en C ++, las personas de un fondo de C asumen que "referencia" debe ser lo mismo que "puntero" en su lenguaje, y así sucesivamente. Si es correcto decir que Java pasa por referencia realmente depende de lo que se entiende por "referencia".
Gravity

119
Intento utilizar de manera coherente la terminología que se encuentra en el artículo Estrategia de evaluación . Cabe señalar que, a pesar de que el artículo señala los términos varían en gran medida por la comunidad, se hace hincapié en que la semántica para call-by-valuey call-by-referencedifieren de una manera muy importante . (Personalmente, prefiero usar call-by-object-sharingestos días call-by-value[-of-the-reference], ya que esto describe la semántica en un alto nivel y no crea un conflicto con call-by-value, que es la implementación subyacente).

59
@Gravity: ¿Puedes ir y poner tu comentario en una enorme cartelera o algo así? Ese es todo el problema en pocas palabras. Y muestra que todo esto es semántico. Si no estamos de acuerdo con la definición básica de una referencia, no estaremos de acuerdo con la respuesta a esta pregunta :)
MadConan

30
Creo que la confusión es "pasar por referencia" versus "semántica de referencia". Java es paso por valor con semántica de referencia.
rociar

11
@Gravity, si bien tienes toda la razón en que las personas que vienen de C ++ tendrán instintivamente un conjunto de intuiciones diferente con respecto al término "referencia", personalmente creo que el cuerpo está más enterrado en el "por". "Pasar por" es confuso porque es absolutamente distinto de "Pasar un" en Java. En C ++, sin embargo, coloquialmente no lo es. En C ++ puede decir "pasar una referencia", y se entiende que pasará la swap(x,y)prueba .

Respuestas:


5844

Java siempre es paso por valor . Desafortunadamente, cuando pasamos el valor de un objeto, le pasamos la referencia . Esto es confuso para los principiantes.

Dice así:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    Dog oldDog = aDog;

    // 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
    aDog == oldDog; // true
}

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()aún regresará "Max". El valor aDogdentro mainno se cambia en la función foocon el Dog "Fifi"como la referencia del objeto se pasa por valor. Si se pasa por referencia, a continuación, el aDog.getName()en mainque volvería "Fifi"después de la llamada a foo.

Igualmente:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    Dog oldDog = aDog;

    foo(aDog);
    // when foo(...) returns, the name of the dog has been changed to "Fifi"
    aDog.getName().equals("Fifi"); // true
    // but it is still the same dog:
    aDog == oldDog; // 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, Fifies 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 se foolleva a cabo en dson tales que, a efectos prácticos, que se realizan en aDog, pero es no es posible cambiar el valor de la variable aDogen sí.


263
¿No es un poco confuso el problema con los detalles internos? No existe una diferencia conceptual entre "pasar una referencia" y "pasar el valor de una referencia", suponiendo que se refiera al "valor del puntero interno al objeto".
izb

383
Pero hay una sutil diferencia. Mira el primer ejemplo. Si fuera puramente por referencia, aDog.name sería "Fifi". No lo es: la referencia que está obteniendo es una referencia de valor que, si se sobrescribe, se restaurará al salir de la función.
erlando

300
@Lorenzo: No, en Java todo se pasa por valor. Las primitivas se pasan por valor, y las referencias a objetos se pasan por valor. Los objetos en sí mismos nunca se pasan a un método, pero los objetos siempre están en el montón y solo se pasa al método una referencia al objeto.
Esko Luontola

295
Mi intento de una buena manera de visualizar el paso de objetos: imagina un globo. Llamar a un fxn es como atar una segunda cuerda al globo y pasar la línea al fxn. parámetro = new Balloon () cortará esa cadena y creará un nuevo globo (pero esto no tiene efecto en el globo original). parameter.pop () todavía lo mostrará porque sigue la cadena al mismo globo original. Java es pasar por valor, pero el valor pasado no es profundo, está en el nivel más alto, es decir, un primitivo o un puntero. No confunda eso con un paso profundo por valor donde el objeto está completamente clonado y pasado.
dhackner

150
Lo que es confuso es que las referencias a objetos son en realidad punteros. Al principio, SUN los llamó punteros. Entonces el marketing informó que "puntero" era una mala palabra. Pero aún ve la nomenclatura "correcta" en NullPointerException.
contrato del Prof. Falken incumplió

3035

Acabo de notar que hiciste referencia a mi artículo .

La especificación de Java dice que todo en Java es paso por valor. No hay tal cosa como "pasar por referencia" en Java.

La clave para entender esto es que algo así como

Dog myDog;

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

Lo que eso significa es cuando tienes

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

básicamente está pasando la dirección del Dogobjeto creado al foomé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 el Dogobjeto reside en la dirección de memoria 42. Esto significa que pasamos 42 al método.

si el Método se definiera 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 someDogse establece en el valor 42
  • en la línea "AAA"
    • someDogse sigue hasta Dogque señala (el Dogobjeto en la dirección 42)
    • a ese Dog(el de la dirección 42) se le pide que cambie su nombre a Max
  • en la línea "BBB"
    • DogSe crea un nuevo . Digamos que está en la dirección 74
    • asignamos el parámetro someDoga 74
  • en la línea "CCC"
    • someDog se sigue hasta Dogdonde apunta (el Dogobjeto en la dirección 74)
    • Dogse le pide a ese (el de la dirección 74) que cambie su nombre a Rowlf
  • entonces volvemos

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

¿ myDogCambio?

Ahí está la llave.

Teniendo en cuenta que myDoges un puntero , y no real Dog, la respuesta es NO. myDogtodavía tiene el valor 42; sigue apuntando al original Dog(pero tenga en cuenta que debido a la línea "AAA", su nombre ahora es "Max", sigue siendo el mismo perro; myDogel valor no ha cambiado).

Es perfectamente válido 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 apuntados. Sin embargo, no puede cambiar dónde apunta ese puntero.

En C ++, Ada, Pascal y otros lenguajes que admiten paso por referencia, en realidad puede cambiar la variable que se pasó.

Si Java tuviera una semántica de paso por referencia, el foométodo que definimos anteriormente habría cambiado hacia dónde myDogapuntaba cuando se asignó someDogen la línea BBB.

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


164
Es por eso que el refrán común "Java no tiene punteros" es tan engañoso.
Beska el

134
Estás equivocado, en mi humilde opinión. "Teniendo en cuenta que myDog es un puntero, y no un perro real, la respuesta es NO. MyDog todavía tiene el valor 42; todavía apunta al perro original". myDog tiene el valor 42 pero su argumento de nombre ahora contiene "Max", en lugar de "Rover" en la línea // AAA.
Özgür

180
Piensa en ello de esta manera. Alguien tiene la dirección de Ann Arbor, MI (mi ciudad natal, GO BLUE!) En un trozo de papel llamado "annArborLocation". Lo copia en una hoja de papel llamada "myDestination". Puede conducir a "myDestination" y plantar un árbol. Es posible que haya cambiado algo sobre la ciudad en ese lugar, pero no cambia el LAT / LON que se escribió en cualquier papel. Puede cambiar el LAT / LON en "myDestination" pero no cambia "annArborLocation". ¿Eso ayuda?
Scott Stanchfield el

43
@Scott Stanchfield: leí tu artículo hace aproximadamente un año y realmente me ayudó a aclarar las cosas. ¡Gracias! Puedo humildemente sugerir una pequeña adición: debe mencionar que en realidad hay un término específico que describe esta forma de "llamada por valor donde el valor es una referencia" que fue inventada por Barbara Liskov para describir la estrategia de evaluación de su lenguaje CLU en 1974, para evitar confusiones como la que aborda su artículo: llamada por compartir (a veces llamada llamada por compartir objetos o simplemente llamada por objeto ), que describe casi perfectamente la semántica.
Jörg W Mittag

29
@Gevorg: los lenguajes C / C ++ no poseen el concepto de "puntero". Hay otros lenguajes que usan punteros pero no permiten los mismos tipos de manipulación de punteros que permiten C / C ++. Java tiene punteros; solo están protegidos contra las travesuras.
Scott Stanchfield

1741

Java siempre pasa argumentos por valor , NO por referencia.


Déjame explicarte 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. Declarar una referencia llamada fde tipo Fooy asignarle un nuevo objeto de tipo Foocon un atributo "f".

    Foo f = new Foo("f");

    ingrese la descripción de la imagen aquí

  2. Desde el lado del método, se declara una referencia de tipo Foocon un nombre ay se asigna inicialmente null.

    public static void changeReference(Foo a)

    ingrese la descripción de la imagen aquí

  3. Cuando llame al método changeReference, a la referencia ase le asignará el objeto que se pasa como argumento.

    changeReference(f);

    ingrese la descripción de la imagen aquí

  4. Declarar una referencia llamada bde tipo Fooy asignarle un nuevo objeto de tipo Foocon un atributo "b".

    Foo b = new Foo("b");

    ingrese la descripción de la imagen aquí

  5. a = brealiza una nueva asignación a la referencia a, no f , del objeto cuyo atributo es "b".

    ingrese la descripción de la imagen aquí

  6. A medida que llama al modifyReference(Foo c)método, cse crea una referencia y se le asigna el objeto con atributo "f".

    ingrese la descripción de la imagen aquí

  7. c.setAttribute("c");cambiará el atributo del objeto que hace referencia ca él, y es el mismo objeto que hace referencia fa él.

    ingrese la descripción de la imagen aquí

Espero que entiendas ahora cómo pasar objetos como argumentos funciona en Java :)


82
+1 cosas bonitas. buenos diagramas También encontré una buena página sucinta aquí adp-gmbh.ch/php/pass_by_reference.html OK Admito que está escrita en PHP, pero es el principio de entender la diferencia lo que creo que es importante (y cómo manipular esa diferencia para tus necesidades).
DaveM

55
@ Eng.Fouad Es una buena explicación, pero si aapunta al mismo objeto que f(y nunca obtiene su propia copia del objeto f), cualquier cambio en el objeto que se haga adebe modificarse ftambién (ya que ambos trabajan con el mismo objeto ), por lo que en algún momento adebe obtener su propia copia de los fpuntos de objeto .
0x6C38

14
@MrD cuando 'a' apunta al mismo objeto 'f' también apunta, entonces cualquier cambio realizado a ese objeto a través de 'a' también se puede observar a través de 'f', PERO NO CAMBIÓ 'f'. 'f' todavía apunta al mismo objeto. Puede cambiar totalmente el objeto, pero nunca puede cambiar a qué apunta 'f'. Este es el problema fundamental que por alguna razón algunas personas simplemente no pueden comprender.
Mike Braun

@MikeBraun ... ¿qué? Ahora me has confundido: S. ¿No es lo que acaba de escribir contrario a lo que muestra 6. y 7.?
Evil Lavadora

66
Esta es la mejor explicación que he encontrado. Por cierto, ¿qué pasa con la situación básica? Por ejemplo, el argumento requiere un tipo int, ¿todavía pasa la copia de una variable int al argumento?
allenwang

733

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

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

Paso dos recuerda que cuando pasas un objeto a un método, estás pasando la referencia del objeto y no el objeto en sí.

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

Ahora piense en lo que hace / es la 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Á 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. Entonces solo estás pasando 3bad086a que es el valor de la referencia.
  4. Estás pasando el valor de la referencia y no la referencia en sí (y no el objeto).
  5. Este valor es 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 persona variable se crea en la línea n. ° 1 y es nula al principio.
  • Se crea un nuevo Objeto Persona en la línea # 2, almacenado en la memoria, y la persona variable recibe la referencia al objeto Persona. Es decir, su dirección. Digamos 3bad086a.
  • La persona variable que posee 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.
  • Revise el comentario en la línea # 5
  • Se crea una variable local del método - anotherReferenceToTheSamePersonObject - y luego viene la magia en la línea # 6:
    • La persona variable / de referencia se copia bit por bit y se pasa a otra referencia al objeto dentro de la función.
    • No se crean nuevas instancias de Persona.
    • Tanto " person " como " anotherReferenceToTheSamePersonObject " tienen el mismo valor de 3bad086a.
    • No intente esto, pero person == anotherReferenceToTheSamePersonObject sería cierto.
    • Ambas variables tienen COPIAS IDÉNTICAS de la referencia y ambas se refieren al mismo Objeto Persona, el MISMO Objeto en el Montón y NO UNA COPIA.

Una imagen vale mas que mil palabras:

Pase por valor

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

Si no lo obtuvo, simplemente confíe en mí y recuerde que es mejor decir que Java es pasar por valor . Bueno, pase por valor de referencia . ¡Oh, bueno, aún mejor es pasar por copia del valor de la variable! ;)

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

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

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

Java es paso por valor porque dentro de un método puede modificar el Objeto referenciado 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 pase lo que pase!


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


¡Por supuesto, puede acortarlo y solo decir que Java es paso por valor!


55
¿Quiere decir punteros? .. Si lo entiendo correctamente, en public void foo(Car car){ ... }, cares local fooy contiene la ubicación del montón del Objeto? Entonces, si cambio carel valor por car = new Car(), ¿apuntará a un Objeto diferente en el montón? y si cambio carel valor de la propiedad por car.Color = "Red", carse modificará el objeto en el montón señalado por Además, ¿es lo mismo en C #? ¡Por favor responde! ¡Gracias!
dpp

11
@domanokz Me estás matando, ¡por favor no vuelvas a decir esa palabra! ;) Tenga en cuenta que podría haber respondido a esta pregunta sin decir 'referencia' también. Es un problema de terminología y las p empeoran las cosas. Yo y Scot tenemos diferentes puntos de vista sobre esto lamentablemente. Creo que entendiste cómo funciona en Java, ahora puedes llamarlo pasar por valor, por compartir objetos, por copia del valor de la variable, ¡o siéntete libre de pensar en otra cosa! Realmente no me importa mientras entiendas cómo funciona y qué hay en una variable de tipo Objeto: ¡solo una dirección de apartado de correos! ;)
Marsellus Wallace

99
¿Parece que acabas de pasar una referencia ? Voy a defender el hecho de que Java sigue siendo un lenguaje copiado de paso por referencia. El hecho de que sea una referencia copiada no cambia la terminología. Ambas referencias todavía apuntan al mismo objeto. Este es un argumento purista ...
John Strickler

3
Entonces, pase a la línea teórica # 9, System.out.println(person.getName());¿qué aparecerá? "Tom" o "Jerry"? Esto es lo último que me ayudará a superar esta confusión.
TheBrenny

1
"El valor de referencia " es lo que finalmente me lo explicó.
Alexander Shubert

689

Java siempre se pasa por valor, sin excepciones, nunca .

Entonces, ¿cómo es que cualquiera puede estar confundido por esto y creer que Java se pasa por referencia, o cree que tiene un ejemplo de Java que actúa como pase por referencia? El punto clave es que Java nunca proporciona acceso directo a los valores de los objetos , en ninguna circunstancia. El único acceso a los objetos es a través de una referencia a ese objeto. Debido a que siempre se accede a los objetos Java a través de una referencia, en lugar de hacerlo directamente, es común hablar de campos y variables y argumentos de métodos como objetos , cuando pedagógicamente solo son referencias a objetos .La confusión surge de este cambio (estrictamente hablando, incorrecto) en la nomenclatura.

Entonces, cuando se llama a un método

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

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

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


77
Como los valores de las primitivas son inmutables (como String), la diferencia entre los dos casos no es realmente relevante.
Paŭlo Ebermann

44
Exactamente. Por todo lo que puede ver a través de un comportamiento JVM observable, las primitivas podrían pasarse por referencia y podrían vivir en el montón. No lo hacen, pero eso no es realmente observable de ninguna manera.
Gravity

66
primitivas son inmutables? ¿Es eso nuevo en Java 7?
user85421

44
Los punteros son inmutables, los primitivos en general son mutables. La cadena tampoco es una primitiva, es un objeto. Además, la estructura subyacente de la cadena es una matriz mutable. Lo único inmutable es la longitud, que es la naturaleza inherente de las matrices.
kingfrito_5005

3
Esta es otra respuesta que señala la naturaleza semántica del argumento. La definición de referencia proporcionada en esta respuesta haría que Java "pasara por referencia". El autor esencialmente admite lo mismo en el último párrafo al afirmar que es "indistinguible en la práctica" de "pasar por referencia". Dudo que OP pregunte por el deseo de comprender la implementación de Java, sino de entender cómo usar Java correctamente. Si fuera indistinguible en la práctica, entonces no tendría sentido preocuparse e incluso pensar en ello sería una pérdida de tiempo.
Loduwijk

331

Java pasa referencias por valor.

Por lo tanto, no puede cambiar la referencia que se pasa.


27
Pero lo que se repite constantemente "no se puede cambiar el valor de los objetos pasados ​​en argumentos" es claramente falso. Es posible que no pueda hacer que se refieran a un objeto diferente, pero puede cambiar su contenido llamando a sus métodos. OMI, esto significa que pierde todos los beneficios de las referencias y no obtiene garantías adicionales.
Timmmm

34
Nunca dije "no se puede cambiar el valor de los objetos pasados ​​en argumentos". Diré "No puede cambiar el valor de la referencia de objeto que se pasa como argumento de método", que es una declaración verdadera sobre el lenguaje Java. Obviamente, puede cambiar el estado del objeto (siempre que no sea inmutable).
ScArcher2

20
Tenga en cuenta que en realidad no puede pasar objetos en Java; Los objetos permanecen en el montón. Se pueden pasar punteros a los objetos (que se copian en el marco de la pila para el método llamado). Por lo tanto, nunca cambia el valor pasado (el puntero), pero es libre de seguirlo y cambiar el elemento en el montón al que apunta. Eso es pasar por valor.
Scott Stanchfield

10
¿Parece que acabas de pasar una referencia ? Voy a defender el hecho de que Java sigue siendo un lenguaje copiado de paso por referencia. El hecho de que sea una referencia copiada no cambia la terminología. Ambas referencias todavía apuntan al mismo objeto. Este es un argumento purista ...
John Strickler

3
Java no pasa un objeto, pasa el valor del puntero al objeto. Esto crea un nuevo puntero a esa ubicación de memoria del objeto original en una nueva variable. Si cambia el valor (la dirección de memoria a la que apunta) de esta variable de puntero en un método, entonces el puntero original utilizado en la persona que llama al método no se modifica. Si llama al parámetro una referencia, el hecho de que sea una copia de la referencia original, no la referencia original en sí misma, de modo que ahora haya dos referencias al objeto, significa que se pasa por valor
theferrit32

238

Siento que discutir sobre "pasar por referencia vs pasar por valor" no es muy útil.

Si dice: "Java es pasar por lo que sea (referencia / valor)", en cualquier caso, no proporcionará una respuesta completa. Aquí hay información adicional que con suerte ayudará a comprender lo que está sucediendo en la memoria.

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

Bueno. En primer lugar, las primitivas locales van a la pila. Entonces este código:

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

resultados en esto:

primitivas en la pila

Cuando declaras e instancias un objeto. El objeto real va al 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 en la pila.

Al igual que:

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

ab * 7ch no es uno!

Una matriz es un objeto, por lo que también va en el montón. ¿Y qué hay de los objetos en la matriz? Obtienen su propio espacio de almacenamiento dinámico 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");

hermanos marx

Entonces, ¿qué pasa cuando llamas a un método? Si pasa un objeto, lo que realmente está pasando es la dirección del objeto. Algunos pueden decir el "valor" de la dirección, y otros dicen que es solo una referencia al objeto. Esta es la génesis de la guerra santa entre los defensores de la "referencia" y el "valor". Lo que llamas no es tan importante como que entiendas que lo que se pasa 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 asigna el identificador hisName, ya que la dirección de la segunda cadena es la misma que la primera, no se crea una nueva cadena y no se asigna nuevo espacio de almacenamiento dinámico, pero se crea un nuevo identificador en la pila. Luego llamamos shout(): se crea un nuevo marco de pila y se crea un nuevo identificador, namey se le asigna la dirección de la Cadena ya existente.

la da di da da da da

Entonces, ¿valor, referencia? Tú dices "papa".


77
Sin embargo, debería haber seguido con un ejemplo más complejo donde una función parece alterar una variable a cuya dirección tiene una referencia.
Brian Peterson el

34
La gente no está "bailando sobre el problema real" de stack vs heap, porque ese no es el problema real. Es un detalle de implementación en el mejor de los casos, y francamente incorrecto en el peor. (Es bastante posible que los objetos vivan en la pila; google "análisis de escape". Y una gran cantidad de objetos contienen primitivas que probablemente no viven en la pila.) El problema real es exactamente la diferencia entre los tipos de referencia y los tipos de valor - en particular, que el valor de una variable de tipo de referencia es una referencia, no el objeto al que se refiere.
cHao

8
Es un "detalle de implementación" en el sentido de que nunca se requiere que Java muestre realmente dónde vive un objeto en la memoria, y de hecho parece decidido a evitar filtrar esa información. Podría poner el objeto en la pila, y nunca lo sabrías. Si te importa, te estás enfocando en lo incorrecto, y en este caso, eso significa ignorar el problema real.
cHao

10
Y de cualquier manera, "las primitivas van a la pila" es incorrecta. Las variables locales primitivas van a la pila. (Si no se han optimizado, por supuesto). Pero entonces, también lo hacen las variables de referencia locales . Y los miembros primitivos definidos dentro de un objeto viven donde sea que viva el objeto.
cHao

99
De acuerdo con los comentarios aquí. Stack / heap es un problema secundario y no es relevante. Algunas variables pueden estar en la pila, algunas están en la memoria estática (variables estáticas) y muchas viven en el montón (todas las variables miembros del objeto). NINGUNA de estas variables se puede pasar por referencia: desde un método llamado NUNCA es posible cambiar el valor de una variable que se pasa como argumento. Por lo tanto, no hay paso por referencia en Java.
fishinear

195

Solo para mostrar el contraste, compare los siguientes fragmentos de C ++ y Java :

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 paso: por valor para los tipos integrados y por valor del puntero para los tipos de objeto.


77
+1 También agregaría Dog **objPtrPtral ejemplo de C ++, de esa manera podemos modificar lo que el puntero "señala".
Amro

1
Pero esto no responde a la pregunta dada en Java. De hecho, todo en el método de Java es Pass By Value, nada más.
tauitdnmd

2
Este ejemplo solo prueba que Java usa el equivalente del puntero en C no? ¿Dónde pasar por valor en C son básicamente de solo lectura? Al final, todo este hilo es incomprensible
amdev


166

Básicamente, la reasignación de parámetros de Object no afecta el argumento, por ejemplo,

private void foo(Object bar) {
    bar = null;
}

public static void main(String[] args) {
    String baz = "Hah!";
    foo(baz);
    System.out.println(baz);
}

se imprimirá en "Hah!"lugar de null. La razón por la que esto funciona es porque bares una copia del valor de baz, que es solo una referencia "Hah!". Si se tratara de la referencia real en sí, entonces foohabría redefinido baza null.


8
Prefiero decir que bar es una copia de la referencia baz (o alias baz), que apunta inicialmente al mismo objeto.
MaxZoom

¿No hay una ligera diferencia entre la clase String y todas las demás clases?
Mehdi Karamosly

152

No puedo creer que nadie haya mencionado a Barbara Liskov todavía. Cuando diseñó CLU en 1974, se encontró con este mismo problema de terminología e inventó el término llamada por compartir (también conocido como llamada por compartir objetos y llamada por objeto ) para este caso específico de "llamada por valor donde el valor es una referencia".


3
Me gusta esta distinción en nomenclatura. Es desafortunado que Java admita llamadas compartiendo objetos, pero no llamadas por valor (como lo hace C ++). Java admite llamadas por valor solo para tipos de datos primitivos y no tipos de datos compuestos.
Derek Mahar

2
Realmente no creo que necesitáramos un término adicional, es simplemente pasar por valor para un tipo específico de valor. ¿Agregar "llamar por primitivo" agregaría alguna aclaración?
Scott Stanchfield


Entonces, ¿puedo pasar por referencia compartiendo algún objeto de contexto global o incluso pasando un objeto de contexto que contiene otras referencias? Todavía paso por valor, pero al menos tengo acceso a referencias que puedo modificar y hacer que apunten a otra cosa.
YoYo

1
@Sanjeev: el intercambio de llamadas por objeto es un caso especial de paso por valor. Sin embargo, mucha gente argumenta con vehemencia que Java (y lenguajes similares como Python, Ruby, ECMAScript, Smalltalk) son pasados ​​por referencia. Yo preferiría llamarlo pasan por valor, también, pero llamada por objeto el intercambio parece ser un término razonable de que incluso las personas que afirman que se no pasan por valor pueden acordar.
Jörg W Mittag

119

El quid de la cuestión es que la palabra referencia en la expresión "pasar por referencia" significa algo completamente diferente del significado habitual de la palabra referencia en Java.

Generalmente en Java, referencia significa una referencia a un objeto . Pero los términos técnicos pasan por referencia / valor de la teoría del lenguaje de programación hablando de una referencia a la celda de memoria que contiene la variable , que es algo completamente diferente.


77
@Gevorg - Entonces, ¿qué es una "NullPointerException"?
Hot Licks

55
@Hot: Una excepción desafortunadamente mencionada antes de que Java se decidiera por una terminología clara. La excepción semánticamente equivalente en c # se llama NullReferenceException.
JacquesB

44
Siempre me pareció que el uso de "referencia" en la terminología de Java es una afectación que dificulta la comprensión.
Hot Licks

Aprendí a llamar a estos llamados "punteros a objetos" como un "identificador para el objeto". Esto redujo la ambigüedad.
moronkreacionz

86

En java todo es referencia, así que cuando tienes algo como: Point pnt1 = new Point(0,0);Java hace lo siguiente:

  1. Crea un nuevo objeto Point
  2. Crea una nueva referencia de Punto e inicializa esa referencia a un punto (consulte) en un objeto de Punto creado previamente.
  3. Desde aquí, a través de la vida del objeto Point, accederá a ese objeto a través de la referencia pnt1. Entonces podemos decir que en Java manipulas objetos a través de su referencia.

ingrese la descripción de la imagen aquí

Java no pasa argumentos de método por referencia; los pasa por valor. Usaré ejemplos de este sitio :

public static void tricky(Point arg1, Point arg2) {
  arg1.x = 100;
  arg1.y = 100;
  Point temp = arg1;
  arg1 = arg2;
  arg2 = temp;
}
public static void main(String [] args) {
  Point pnt1 = new Point(0,0);
  Point pnt2 = new Point(0,0);
  System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y); 
  System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
  System.out.println(" ");
  tricky(pnt1,pnt2);
  System.out.println("X1: " + pnt1.x + " Y1:" + pnt1.y); 
  System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);  
}

Flujo del programa:

Point pnt1 = new Point(0,0);
Point pnt2 = new Point(0,0);

Crear dos objetos Point diferentes con dos referencias diferentes asociadas. ingrese la descripción de la imagen aquí

System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y); 
System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
System.out.println(" ");

Como resultado esperado será:

X1: 0     Y1: 0
X2: 0     Y2: 0

En esta línea, el "paso por valor" entra en juego ...

tricky(pnt1,pnt2);           public void tricky(Point arg1, Point arg2);

Referencias pnt1y pnt2se pasan por valor al método complicado, lo que significa que ahora las referencias tuyas pnt1y pnt2tienen su copiesnombre arg1y arg2.Así pnt1y arg1 apunta al mismo objeto. (Lo mismo para el pnt2y arg2) ingrese la descripción de la imagen aquí

En el trickymétodo:

 arg1.x = 100;
 arg1.y = 100;

ingrese la descripción de la imagen aquí

Siguiente en el trickymétodo

Point temp = arg1;
arg1 = arg2;
arg2 = temp;

Aquí, primero debe crear la nueva tempreferencia del punto que apuntar en el mismo lugar como arg1referencia. Luego mueve la referencia arg1para apuntar al mismo lugar como arg2referencia. Finalmente arg2se apunte al mismo lugar como temp.

ingrese la descripción de la imagen aquí

Desde aquí ámbito del trickymétodo se ha ido y que no tienen acceso a los más referencias: arg1, arg2, temp. Pero una nota importante es que todo lo que haga con estas referencias cuando estén 'en la vida' afectará permanentemente el objeto al que apuntan .

Entonces, después de ejecutar el método tricky, cuando regresa main, tiene esta situación: ingrese la descripción de la imagen aquí

Así que ahora, la ejecución completa del programa será:

X1: 0         Y1: 0
X2: 0         Y2: 0
X1: 100       Y1: 100
X2: 0         Y2: 0

77
En java cuando dice "En java todo es referencia" quiere decir que todos los objetos se pasan por referencia. Los tipos de datos primitivos no se pasan por referencia.
Eric

Puedo imprimir el valor intercambiado en el método principal. en el método trickey, agregue la siguiente declaración arg1.x = 1; arg1.y = 1; arg2.x = 2; arg2.y = 2;, así, como arg1 ahora mantiene la referencia pnt2 y arg2 mantiene ahora la referencia pnt1, entonces, su impresiónX1: 2 Y1: 2 X2: 1 Y2: 1
Shahid Ghafoor

85

Java siempre pasa por valor, no pasa por referencia

En primer lugar, debemos entender qué son los pases por valor y los que pasan por referencia.

Pasar por valor significa que está haciendo una copia en memoria del valor del parámetro real que se pasa. Esta es una copia del contenido del parámetro real .

Pasar por referencia (también llamado pasar por dirección) significa que se almacena una copia de la dirección del parámetro real .

A veces, Java puede dar la ilusión de pasar por referencia. Veamos cómo funciona usando el siguiente ejemplo:

public class PassByValue {
    public static void main(String[] args) {
        Test t = new Test();
        t.name = "initialvalue";
        new PassByValue().changeValue(t);
        System.out.println(t.name);
    }

    public void changeValue(Test f) {
        f.name = "changevalue";
    }
}

class Test {
    String name;
}

La salida de este programa es:

changevalue

Vamos a entender paso a paso:

Test t = new Test();

Como todos sabemos, creará un objeto en el montón y devolverá el valor de referencia a t. Por ejemplo, suponga que el valor de t es 0x100234(no conocemos el valor interno real de JVM, esto es solo un ejemplo).

primera ilustración

new PassByValue().changeValue(t);

Al pasar la referencia t a la función, no pasará directamente el valor de referencia real de la prueba de objeto, pero creará una copia de t y luego la pasará a la función. Como pasa por valor , pasa una copia de la variable en lugar de la referencia real de la misma. Como dijimos que el valor de t era 0x100234, tanto t como f tendrán el mismo valor y, por lo tanto, apuntarán al mismo objeto.

segunda ilustración

Si cambia algo en la función utilizando la referencia f, modificará el contenido existente del objeto. Es por eso que obtuvimos el resultado changevalue, que se actualiza en la función.

Para entender esto más claramente, considere el siguiente ejemplo:

public class PassByValue {
    public static void main(String[] args) {
        Test t = new Test();
        t.name = "initialvalue";
        new PassByValue().changeRefence(t);
        System.out.println(t.name);
    }

    public void changeRefence(Test f) {
        f = null;
    }
}

class Test {
    String name;
}

¿Esto arrojará un NullPointerException? No, porque solo pasa una copia de la referencia. En el caso de pasar por referencia, podría haber arrojado un NullPointerException, como se ve a continuación:

tercera ilustración

Esperemos que esto ayude.


71

Una referencia siempre es un valor cuando se representa, sin importar el idioma que use.

Para obtener una vista fuera del cuadro, veamos el ensamblaje o alguna administración de memoria de bajo nivel. A nivel de CPU, una referencia a cualquier cosa se convierte inmediatamente en un valor si se escribe en la memoria o en uno de los registros de la CPU. (Es por eso que puntero es una buena definición. Es un valor, que tiene un propósito al mismo tiempo).

Los datos en la memoria tienen una ubicación y en esa ubicación hay un valor (byte, palabra, lo que sea). En Assembly tenemos una solución conveniente para asignar un nombre a cierta ubicación (también conocida como variable), pero al compilar el código, el ensamblador simplemente reemplaza Name con la ubicación designada, al igual que su navegador reemplaza los nombres de dominio con direcciones IP.

Hasta el núcleo, es técnicamente imposible pasar una referencia a cualquier cosa en cualquier idioma sin representarlo (cuando se convierte inmediatamente en un valor).

Digamos que tenemos una variable Foo, su ubicación está en el byte 47 en la memoria y su valor es 5. Tenemos otra variable Ref2Foo que está en el 223 byte en la memoria, y su valor será 47. Este Ref2Foo podría ser una variable técnica , no creado explícitamente por el programa. Si solo mira 5 y 47 sin ninguna otra información, verá solo dos valores . Si los usa como referencias, para llegar a ellos 5tenemos que viajar:

(Name)[Location] -> [Value at the Location]
---------------------
(Ref2Foo)[223]  -> 47
(Foo)[47]       -> 5

Así es como funcionan las tablas de salto.

Si queremos llamar a un método / función / procedimiento con el valor de Foo, hay algunas formas posibles de pasar la variable al método, dependiendo del idioma y sus diversos modos de invocación:

  1. 5 se copia en uno de los registros de la CPU (es decir, EAX).
  2. 5 obtiene PUSHd en la pila.
  3. 47 se copia en uno de los registros de la CPU
  4. 47 EMPUJAR a la pila.
  5. 223 se copia en uno de los registros de la CPU.
  6. 223 obtiene PUSHd a la pila.

En todos los casos por encima de un valor, una copia de un valor existente, se ha creado, ahora depende del método de recepción manejarlo. Cuando escribe "Foo" dentro del método, se lee desde EAX, o se desreferencia automáticamente , o se desreferencia doble, el proceso depende de cómo funciona el lenguaje y / o qué dicta el tipo de Foo. Esto queda oculto para el desarrollador hasta que elude el proceso de desreferenciación. Por lo tanto, una referencia es un valor cuando se representa, porque una referencia es un valor que debe procesarse (a nivel de lenguaje).

Ahora hemos pasado Foo al método:

  • en el caso 1. y 2. si cambia Foo ( Foo = 9) solo afecta el alcance local ya que tiene una copia del Valor. Desde el interior del método, ni siquiera podemos determinar en qué lugar de la memoria se encontraba el Foo original.
  • en el caso 3. y 4. si usa construcciones de lenguaje predeterminadas y cambia Foo ( Foo = 11), podría cambiar Foo globalmente (depende del lenguaje, es decir, Java o como la procedure findMin(x, y, z: integer;var m de Pascal : integer);). Sin embargo, si el idioma le permite eludir el proceso de desreferenciación, puede cambiar 47, digamos que 49. En ese momento, Foo parece haber cambiado si lo lees, porque le has cambiado el puntero local . Y si tuviera que modificar este Foo dentro del método ( Foo = 12), probablemente FUBARÁ la ejecución del programa (también conocido como segfault) porque escribirá en una memoria diferente a la esperada, incluso puede modificar un área que está destinada a contener ejecutable programa y escribir en él modificará el código de ejecución (Foo ahora no está en 47). PERO el valor de Foo de47no cambió globalmente, solo el que estaba dentro del método, porque 47también era una copia del método.
  • en el caso 5. y 6. si modifica 223dentro del método, crea el mismo caos que en 3. o 4. (un puntero, apuntando a un valor ahora incorrecto, que nuevamente se usa como puntero) pero sigue siendo un local problema, ya que se copió 223 . Sin embargo, si puede desreferenciar Ref2Foo(es decir 223), alcanzar y modificar el valor señalado 47, digamos, a 49, afectará a Foo globalmente , porque en este caso los métodos obtuvieron una copia 223 pero 47solo existe una referencia , y cambiar eso to 49llevará cada Ref2Foodoble desreferenciación a un valor incorrecto.

Analizando detalles insignificantes, incluso los lenguajes que hacen referencia pasarán valores a las funciones, pero esas funciones saben que tienen que usarlo para fines de referencia. Este paso de la referencia como valor está oculto para el programador porque es prácticamente inútil y la terminología es solo paso por referencia .

El paso estricto por valor también es inútil, significaría que una matriz de 100 Mbytes debería copiarse cada vez que llamamos a un método con la matriz como argumento, por lo tanto, Java no puede pasar estrictamente por valor. Cada idioma pasaría una referencia a esta gran matriz (como valor) y emplea un mecanismo de copia en escritura si esa matriz se puede cambiar localmente dentro del método o permite que el método (como lo hace Java) modifique la matriz globalmente (desde la vista del llamante) y algunos idiomas permiten modificar el valor de la referencia en sí.

En resumen, y en la propia terminología de Java, Java es paso por valor donde el valor puede ser: un valor real o un valor que es una representación de una referencia .


1
En un lenguaje con paso por referencia, lo que se pasa (la referencia) es efímero; no se "supone" que el destinatario lo copie. En Java, pasar una matriz es un "identificador de objeto", equivalente a un trozo de papel que dice "Objeto # 24601", cuando el 24601º objeto construido era una matriz. El destinatario puede copiar el "Objeto # 24601" en cualquier lugar que desee, y cualquier persona con un trozo de papel que diga "Objeto # 24601" puede hacer lo que quiera con cualquiera de los elementos de la matriz. El patrón de bits que se pasa en realidad no diría "Objeto # 24601", por supuesto, pero ...
supercat

... el punto clave es que el destinatario de la identificación de objeto que identifica la matriz puede almacenar esa identificación de objeto donde quiera y dársela a quien quiera, y cualquier destinatario podrá acceder o modificar la matriz siempre que quiere. Por el contrario, si una matriz se pasó por referencia en un lenguaje como Pascal que admite tales cosas, el método llamado podría hacer lo que quisiera con la matriz, pero no podría almacenar la referencia de tal manera que permita que el código modifique la matriz después de que regresó.
supercat

De hecho, en Pascal puede obtener la dirección de cada variable, ya sea pasada por referencia o copiada localmente, addrno tiene tipo, lo @hace con tipo y puede modificar la variable referenciada más tarde (excepto las copiadas localmente). Pero no entiendo por qué harías eso. Ese trozo de papel en su ejemplo (Objeto # 24601) es una referencia, su propósito es ayudar a encontrar la matriz en la memoria, no contiene ningún dato de matriz en sí mismo. Si reinicia su programa, la misma matriz puede obtener una ID de objeto diferente, incluso si su contenido será el mismo que en la ejecución anterior.
karatedog

Pensé que el operador "@" no era parte de Pascal estándar, pero se implementó como una extensión común. ¿Es parte del estándar? Mi punto fue que en un lenguaje con verdadero paso por referencia y sin capacidad de construir un puntero no efímero a un objeto efímero, código que contiene la única referencia a una matriz, en cualquier parte del universo, antes de pasar la matriz por la referencia puede saber que, a menos que el destinatario "haga trampa", seguirá teniendo la única referencia después. La única forma segura de lograr eso en Java sería construir un objeto temporal ...
supercat

... que encapsula AtomicReferencey no expone la referencia ni su objetivo, sino que incluye métodos para hacer cosas al objetivo; una vez que el código al que se pasó el objeto regresa, el AtomicReference[al que su creador mantuvo una referencia directa] debe ser invalidado y abandonado. Eso proporcionaría la semántica adecuada, pero sería lento y repulsivo.
supercat

70

Java es una llamada por valor

Cómo funciona

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

  • Si se trata de un tipo de datos primitivos, estos bits contienen el valor del tipo de datos primitivos en sí, por eso si cambiamos el valor del encabezado dentro del método, entonces no refleja los cambios externos.

  • Si se trata de un tipo de datos de objeto como Foo foo = new Foo () , en este caso la copia de la dirección del objeto pasa como acceso directo de archivo, supongamos que tenemos un archivo de texto abc.txt en C: \ escritorio y supongamos que hacemos un acceso directo de el mismo archivo y colóquelo dentro de C: \ desktop \ abc-shortcut para que cuando acceda al archivo desde C: \ desktop \ abc.txt y escriba 'Stack Overflow' y cierre el archivo y nuevamente abra el archivo desde el acceso directo y luego escribir 'es la comunidad en línea más grande para que los programadores aprendan', luego el cambio total de archivos será 'Stack Overflow es la comunidad en línea más grande para que los programadores aprendan'lo que significa que no importa desde dónde abra el archivo, cada vez que accedemos al mismo archivo, aquí podemos asumir Foo como un archivo y suponer foo almacenado a 123hd7h (dirección original como C: \ desktop \ abc.txt ) dirección y 234jdid (dirección copiada como C: \ desktop \ abc-shortcut que en realidad contiene la dirección original del archivo dentro). Entonces, para una mejor comprensión, crea un archivo de acceso directo y siente ..


66

Ya hay excelentes respuestas que cubren esto. Quería hacer una pequeña contribución compartiendo un ejemplo muy simple (que compilará) contrastando los comportamientos entre Pass-by-reference en c ++ y Pass-by-value en Java.

Algunos puntos:

  1. El término "referencia" está sobrecargado con dos significados separados. En Java simplemente significa un puntero, pero en el contexto de "Pasar por referencia" significa un identificador de la variable original que se pasó.
  2. Java es paso por valor . Java es un descendiente de C (entre otros lenguajes). Antes de C, varios (pero no todos) lenguajes anteriores como FORTRAN y COBOL soportaban PBR, pero C no. PBR permitió que estos otros lenguajes hicieran cambios en las variables pasadas dentro de las subrutinas. Para lograr lo mismo (es decir, cambiar los valores de las variables dentro de las funciones), los programadores de C pasaron punteros a variables en funciones. Los lenguajes inspirados en C, como Java, tomaron prestada esta idea y continúan pasando el puntero a los métodos como lo hizo C, excepto que Java llama a sus punteros Referencias. Nuevamente, este es un uso diferente de la palabra "Referencia" que en "Pass-By-Reference".
  3. C ++ permite el paso por referencia declarando un parámetro de referencia utilizando el carácter "&" (que resulta ser el mismo carácter utilizado para indicar "la dirección de una variable" en C y C ++). Por ejemplo, si pasamos un puntero por referencia, el parámetro y el argumento no solo apuntan al mismo objeto. Más bien, son la misma variable. Si uno se establece en una dirección diferente o nula, también lo hace el otro.
  4. En el siguiente ejemplo de C ++, paso un puntero a una cadena terminada en nulo por referencia . Y en el siguiente ejemplo de Java, estoy pasando una referencia de Java a una Cadena (de nuevo, lo mismo que un puntero a una Cadena) por valor. Observe el resultado en los comentarios.

C ++ pase por ejemplo de referencia:

using namespace std;
#include <iostream>

void change (char *&str){   // the '&' makes this a reference parameter
    str = NULL;
}

int main()
{
    char *str = "not Null";
    change(str);
    cout<<"str is " << str;      // ==>str is <null>
}

Java pasar "una referencia de Java" por ejemplo de valor

public class ValueDemo{

    public void change (String str){
        str = null;
    }

     public static void main(String []args){
        ValueDemo vd = new ValueDemo();
        String str = "not null";
        vd.change(str);
        System.out.println("str is " + str);    // ==> str is not null!!
                                                // Note that if "str" was
                                                // passed-by-reference, it
                                                // WOULD BE NULL after the
                                                // call to change().
     }
}

EDITAR

Varias personas han escrito comentarios que parecen indicar que o no están viendo mis ejemplos o no obtienen el ejemplo de c ++. No estoy seguro de dónde está la desconexión, pero adivinar el ejemplo de c ++ no está claro. Estoy publicando el mismo ejemplo en pascal porque creo que el paso por referencia se ve más limpio en pascal, pero podría estar equivocado. Podría estar confundiendo más a la gente; Espero que no.

En pascal, los parámetros pasados ​​por referencia se denominan "parámetros var". En el siguiente procedimiento setToNil, tenga en cuenta la palabra clave 'var' que precede al parámetro 'ptr'. Cuando se pasa un puntero a este procedimiento, se pasará por referencia . Tenga en cuenta el comportamiento: cuando este procedimiento establece ptr en nil (eso es hablar pascal para NULL), establecerá el argumento en nil; no puede hacerlo en Java.

program passByRefDemo;
type 
   iptr = ^integer;
var
   ptr: iptr;

   procedure setToNil(var ptr : iptr);
   begin
       ptr := nil;
   end;

begin
   new(ptr);
   ptr^ := 10;
   setToNil(ptr);
   if (ptr = nil) then
       writeln('ptr seems to be nil');     { ptr should be nil, so this line will run. }
end.

EDITAR 2

Algunos extractos de "THE Java Programming Language" de Ken Arnold, James Gosling (el tipo que inventó Java) y David Holmes, capítulo 2, sección 2.6.5

Todos los parámetros a los métodos se pasan "por valor" . En otras palabras, los valores de las variables de parámetros en un método son copias del invocador especificado como argumentos.

Continúa haciendo el mismo punto con respecto a los objetos. . .

Debe tener en cuenta que cuando el parámetro es una referencia de objeto, es la referencia del objeto, no el objeto en sí, lo que se pasa "por valor" .

Y hacia el final de la misma sección, hace una declaración más amplia sobre que Java solo pasa por valor y nunca pasa por referencia.

El lenguaje de programación Java no pasa objetos por referencia; que pasa a referencias a objetos por valor . Debido a que dos copias de la misma referencia se refieren al mismo objeto real, los cambios realizados a través de una variable de referencia son visibles a través de la otra. Hay exactamente un modo de paso de parámetros, pasar por valor, y eso ayuda a simplificar las cosas.

Esta sección del libro tiene una gran explicación del paso de parámetros en Java y de la distinción entre paso por referencia y paso por valor y es por el creador de Java. Animaría a cualquiera a leerlo, especialmente si todavía no estás convencido.

Creo que la diferencia entre los dos modelos es muy sutil y, a menos que haya hecho la programación en la que realmente utilizó el paso por referencia, es fácil pasar por alto dónde difieren dos modelos.

Espero que esto resuelva el debate, pero probablemente no.

EDITAR 3

Podría estar un poco obsesionado con esta publicación. Probablemente porque siento que los creadores de Java sin saberlo difundieron información errónea. Si en lugar de usar la palabra "referencia" para los punteros hubieran usado otra cosa, digamos dingleberry, no habría habido ningún problema. Se podría decir, "Java pasa dingleberries por valor y no por referencia", y nadie se confundiría.

Esa es la razón por la que solo los desarrolladores de Java tienen problemas con esto. Miran la palabra "referencia" y piensan que saben exactamente lo que eso significa, por lo que ni siquiera se molestan en considerar el argumento opuesto.

De todos modos, noté un comentario en una publicación anterior, que hizo una analogía de globo que realmente me gustó. Tanto es así que decidí unir algunas imágenes prediseñadas para hacer un conjunto de dibujos animados para ilustrar el punto.

Pasar una referencia por valor: los cambios a la referencia no se reflejan en el alcance de la persona que llama, pero los cambios en el objeto sí. Esto se debe a que la referencia se copia, pero tanto el original como la copia se refieren al mismo objeto. Pasar referencias de objeto por valor

Pasar por referencia : no hay copia de la referencia. La persona que llama y la función que se está llamando comparten la referencia única. Cualquier cambio en la referencia o los datos del Objeto se reflejan en el alcance de la persona que llama. Pase por referencia

EDITAR 4

He visto publicaciones sobre este tema que describen la implementación de bajo nivel de paso de parámetros en Java, lo que creo que es genial y muy útil porque hace concreta una idea abstracta. Sin embargo, para mí la pregunta es más sobre el comportamiento descrito en la especificación del lenguaje que sobre la implementación técnica del comportamiento. Este es un ejercicio de la Especificación del lenguaje Java, sección 8.4.1 :

Cuando se invoca el método o el constructor (§15.12), los valores de las expresiones de argumento reales inicializan las variables de parámetro recién creadas, cada una del tipo declarado, antes de la ejecución del cuerpo del método o constructor. El Identificador que aparece en el DeclaratorId puede usarse como un nombre simple en el cuerpo del método o constructor para referirse al parámetro formal.

Lo que significa que Java crea una copia de los parámetros pasados ​​antes de ejecutar un método. Como la mayoría de las personas que estudiaron compiladores en la universidad, usé "The Dragon Book", que es EL libro de compiladores. Tiene una buena descripción de "Llamada por valor" y "Llamada por referencia" en el Capítulo 1. La descripción de Llamada por valor coincide exactamente con las especificaciones Java.

Cuando estudié compiladores, en los años 90, utilicé la primera edición del libro de 1986, anterior a Java por unos 9 o 10 años. Sin embargo, ¡acabo de encontrar una copia de la 2da Edición del 2007 que en realidad menciona Java! La sección 1.6.6 denominada "Mecanismos de paso de parámetros" describe el paso de parámetros bastante bien. Aquí hay un extracto bajo el título "Llamada por valor" que menciona Java:

En la llamada por valor, el parámetro real se evalúa (si es una expresión) o se copia (si es una variable). El valor se coloca en la ubicación que pertenece al parámetro formal correspondiente del procedimiento llamado. Este método se usa en C y Java, y es una opción común en C ++, así como en la mayoría de los otros lenguajes.


44
Honestamente, puede simplificar esta respuesta diciendo que Java es pasar por valor solo para tipos primitivos. Todo lo que hereda de Object se pasa efectivamente por referencia, donde la referencia es el puntero que está pasando.
Scuba Steve

1
@JuanMendes, acabo de usar C ++ como ejemplo; Podría darte ejemplos en otros idiomas. El término "Pasar por referencia" existía mucho antes de que existiera C ++. Es un término de libro de texto con una definición muy específica. Y por definición, Java no se pasa por referencia. Puede seguir usando el término de esta manera si lo desea, pero su uso no será coherente con la definición del libro de texto. No es solo un puntero a un puntero. Es una construcción proporcionada por el lenguaje para permitir "Pasar por referencia". Mire detenidamente mi ejemplo, pero no confíe en mi palabra y búsquelo usted mismo.
Sanjeev

2
@AutomatedMike Creo que describir Java como "paso por referencia" también es engañoso, sus referencias no son más que punteros. Como Sanjeev escribió valor, referencia y puntero son términos de libros de texto que tienen su propio significado independientemente de lo que usen los creadores de Java.
Jędrzej Dudkiewicz

1
@ArtanisZeratul el punto es sutil, pero no complicado. Por favor, eche un vistazo a la caricatura que publiqué. Sentí que era bastante simple de seguir. Que Java es Pass-by-value no es solo una opinión. Es cierto según la definición del libro de texto de paso por valor. Además, "las personas que siempre dicen que se pasa por valor" incluyen científicos informáticos como James Gosling, el creador de Java. Por favor, vea las citas de su libro en "Editar 2" en mi publicación.
Sanjeev

1
Qué gran respuesta, "pasar por referencia" no existe en Java (y otros idiomas como JS).
newfolder

56

Hasta donde yo sé, Java solo conoce la llamada por valor. Esto significa que para los tipos de datos primitivos trabajará con una copia y para los objetos trabajará con una copia de la referencia a los objetos. Sin embargo, creo que hay algunas trampas; por ejemplo, esto no funcionará:

public static void swap(StringBuffer s1, StringBuffer s2) {
    StringBuffer temp = s1;
    s1 = s2;
    s2 = temp;
}


public static void main(String[] args) {
    StringBuffer s1 = new StringBuffer("Hello");
    StringBuffer s2 = new StringBuffer("World");
    swap(s1, s2);
    System.out.println(s1);
    System.out.println(s2);
}

Esto llenará Hello World y no World Hello porque en la función de intercambio usa copias que no tienen impacto en las referencias en main. Pero si sus objetos no son inmutables, puede cambiarlo, por ejemplo:

public static void appendWorld(StringBuffer s1) {
    s1.append(" World");
}

public static void main(String[] args) {
    StringBuffer s = new StringBuffer("Hello");
    appendWorld(s);
    System.out.println(s);
}

Esto llenará Hello World en la línea de comando. Si cambia StringBuffer en String, producirá solo Hello porque String es inmutable. Por ejemplo:

public static void appendWorld(String s){
    s = s+" World";
}

public static void main(String[] args) {
    String s = new String("Hello");
    appendWorld(s);
    System.out.println(s);
}

Sin embargo, podría hacer un contenedor para String como este que lo haría capaz de usarlo con Strings:

class StringWrapper {
    public String value;

    public StringWrapper(String value) {
        this.value = value;
    }
}

public static void appendWorld(StringWrapper s){
    s.value = s.value +" World";
}

public static void main(String[] args) {
    StringWrapper s = new StringWrapper("Hello");
    appendWorld(s);
    System.out.println(s.value);
}

editar: creo que esta es también la razón para usar StringBuffer cuando se trata de "agregar" dos cadenas porque puedes modificar el objeto original que no puedes con objetos inmutables como String.


+1 para la prueba de intercambio: probablemente la forma más directa y fácil de distinguir entre pasar por referencia y pasar una referencia por valor . Si puede escribir fácilmente una función swap(a, b)que (1) intercambia ay bdesde el punto de vista de la persona que llama, (2) es independiente del tipo en la medida en que lo permite la escritura estática (lo que significa que usarlo con otro tipo no requiere nada más que cambiar los tipos declarados de ay b) , y (3) no requiere que la persona que llama pase explícitamente un puntero o nombre, entonces el lenguaje admite pasar por referencia.
cHao

"... para los tipos de datos primitivos trabajará con una copia y para los objetos trabajará con una copia de la referencia a los objetos" - ¡perfectamente escrito!
Raúl

55

No, no se pasa por referencia.

Java se pasa por valor de acuerdo con la especificación del lenguaje Java:

Cuando se invoca el método o el constructor (§15.12), los valores de las expresiones de argumento reales inicializan las variables de parámetro recién creadas , cada una del tipo declarado, antes de la ejecución del cuerpo del método o constructor. El Identificador que aparece en el DeclaratorId puede usarse como un nombre simple en el cuerpo del método o constructor para referirse al parámetro formal .


52

Permítanme tratar de explicar mi comprensión con la ayuda de cuatro ejemplos. Java es paso por valor y no paso por referencia

/ **

Pasar por valor

En Java, todos los parámetros se pasan por valor, es decir, la asignación de un argumento de método no es visible para la persona que llama.

* /

Ejemplo 1:

public class PassByValueString {
    public static void main(String[] args) {
        new PassByValueString().caller();
    }

    public void caller() {
        String value = "Nikhil";
        boolean valueflag = false;
        String output = method(value, valueflag);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'value' and 'valueflag'
         */
        System.out.println("output : " + output);
        System.out.println("value : " + value);
        System.out.println("valueflag : " + valueflag);

    }

    public String method(String value, boolean valueflag) {
        value = "Anand";
        valueflag = true;
        return "output";
    }
}

Resultado

output : output
value : Nikhil
valueflag : false

Ejemplo 2

/ ** * * Pasar por valor * * /

public class PassByValueNewString {
    public static void main(String[] args) {
        new PassByValueNewString().caller();
    }

    public void caller() {
        String value = new String("Nikhil");
        boolean valueflag = false;
        String output = method(value, valueflag);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'value' and 'valueflag'
         */
        System.out.println("output : " + output);
        System.out.println("value : " + value);
        System.out.println("valueflag : " + valueflag);

    }

    public String method(String value, boolean valueflag) {
        value = "Anand";
        valueflag = true;
        return "output";
    }
}

Resultado

output : output
value : Nikhil
valueflag : false

Ejemplo 3

/ ** Este 'Pase por valor tiene la sensación de' Pasar por referencia '

Algunas personas dicen que los tipos primitivos y 'String' son 'pasar por valor' y los objetos son 'pasar por referencia'.

Pero a partir de este ejemplo, podemos entender que de hecho solo se pasa por valor, teniendo en cuenta que aquí estamos pasando la referencia como valor. es decir: la referencia se pasa por valor. Es por eso que pueden cambiar y aún así es cierto después del alcance local. Pero no podemos cambiar la referencia real fuera del alcance original. lo que eso significa se demuestra en el siguiente ejemplo de PassByValueObjectCase2.

* /

public class PassByValueObjectCase1 {

    private class Student {
        int id;
        String name;
        public Student() {
        }
        public Student(int id, String name) {
            super();
            this.id = id;
            this.name = name;
        }
        public int getId() {
            return id;
        }
        public void setId(int id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        @Override
        public String toString() {
            return "Student [id=" + id + ", name=" + name + "]";
        }
    }

    public static void main(String[] args) {
        new PassByValueObjectCase1().caller();
    }

    public void caller() {
        Student student = new Student(10, "Nikhil");
        String output = method(student);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'student'
         */
        System.out.println("output : " + output);
        System.out.println("student : " + student);
    }

    public String method(Student student) {
        student.setName("Anand");
        return "output";
    }
}

Resultado

output : output
student : Student [id=10, name=Anand]

Ejemplo 4

/ **

Además de lo mencionado en el Ejemplo 3 (PassByValueObjectCase1.java), no podemos cambiar la referencia real fuera del alcance original ".

Nota: no estoy pegando el código private class Student. La definición de clase para Studentes la misma que en Example3.

* /

public class PassByValueObjectCase2 {

    public static void main(String[] args) {
        new PassByValueObjectCase2().caller();
    }

    public void caller() {
        // student has the actual reference to a Student object created
        // can we change this actual reference outside the local scope? Let's see
        Student student = new Student(10, "Nikhil");
        String output = method(student);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'student'
         */
        System.out.println("output : " + output);
        System.out.println("student : " + student); // Will it print Nikhil or Anand?
    }

    public String method(Student student) {
        student = new Student(20, "Anand");
        return "output";
    }

}

Resultado

output : output
student : Student [id=10, name=Nikhil]

49

Nunca puede pasar por referencia en Java, y una de las formas obvias es cuando desea devolver más de un valor de una llamada al método. Considere el siguiente bit de código en C ++:

void getValues(int& arg1, int& arg2) {
    arg1 = 1;
    arg2 = 2;
}
void caller() {
    int x;
    int y;
    getValues(x, y);
    cout << "Result: " << x << " " << y << endl;
}

A veces quieres usar el mismo patrón en Java, pero no puedes; Al menos no directamente. En cambio, podrías hacer algo como esto:

void getValues(int[] arg1, int[] arg2) {
    arg1[0] = 1;
    arg2[0] = 2;
}
void caller() {
    int[] x = new int[1];
    int[] y = new int[1];
    getValues(x, y);
    System.out.println("Result: " + x[0] + " " + y[0]);
}

Como se explicó en respuestas anteriores, en Java está pasando un puntero a la matriz como un valor getValues. Eso es suficiente, porque el método modifica el elemento de matriz y, por convención, espera que el elemento 0 contenga el valor de retorno. Obviamente, puede hacer esto de otras maneras, como estructurar su código para que esto no sea necesario o construir una clase que pueda contener el valor de retorno o permitir que se establezca. Pero el patrón simple disponible para usted en C ++ anterior no está disponible en Java.


48

Pensé que contribuiría con esta respuesta para agregar más detalles de las Especificaciones.

Primero, ¿cuál es la diferencia entre pasar por referencia versus pasar por valor?

Pasar por referencia significa que el parámetro de las funciones llamadas será el mismo que el argumento pasado de los llamantes (no el valor, sino la identidad, la variable en sí misma).

Pasar por valor significa que el parámetro de las funciones llamadas será una copia del argumento pasado de los llamantes.

O de wikipedia, sobre el tema de la referencia de paso

En la evaluación de llamada por referencia (también conocida como paso por referencia), una función recibe una referencia implícita a una variable utilizada como argumento, en lugar de una copia de su valor. Esto normalmente significa que la función puede modificar (es decir, asignarle) la variable utilizada como argumento, algo que verá el llamador.

Y sobre el tema del paso por valor

En la llamada por valor, se evalúa la expresión del argumento y el valor resultante está vinculado a la variable correspondiente en la función [...]. Si la función o procedimiento puede asignar valores a sus parámetros, solo se asigna su copia local [...].

En segundo lugar, necesitamos saber qué utiliza Java en sus invocaciones de métodos. Los estados de la especificación del lenguaje Java

Cuando se invoca el método o el constructor (§15.12), los valores de las expresiones de argumento reales inicializan las variables de parámetro recién creadas , cada una del tipo declarado, antes de la ejecución del cuerpo del método o constructor.

Por lo tanto, asigna (o enlaza) el valor del argumento a la variable de parámetro correspondiente.

¿Cuál es el valor del argumento?

Consideremos los tipos de referencia, los estados de la especificación de máquina virtual Java

Hay tres tipos de tipos de referencia : tipos de clase, tipos de matriz y tipos de interfaz. Sus valores son referencias a instancias de clase, matrices o instancias de clase o matrices creadas dinámicamente que implementan interfaces, respectivamente.

La especificación del lenguaje Java también establece

Los valores de referencia (a menudo solo referencias) son punteros a estos objetos y una referencia nula especial, que no se refiere a ningún objeto.

El valor de un argumento (de algún tipo de referencia) es un puntero a un objeto. Tenga en cuenta que una variable, una invocación de un método con un tipo de referencia tipo de retorno y una expresión de creación de instancia ( new ...) se resuelven en un valor de tipo de referencia.

Entonces

public void method (String param) {}
...
String var = new String("ref");
method(var);
method(var.toString());
method(new String("ref"));

se unen todos el valor de una referencia a un Stringejemplo de parámetro de nueva creación del método, param. Esto es exactamente lo que describe la definición de paso por valor. Como tal, Java es paso por valor .

El hecho de que pueda seguir la referencia para invocar un método o acceder a un campo del objeto referenciado es completamente irrelevante para la conversación. La definición de paso por referencia fue

Esto normalmente significa que la función puede modificar (es decir, asignarle) la variable utilizada como argumento, algo que verá el llamador.

En Java, modificar la variable significa reasignarla. En Java, si reasigna la variable dentro del método, pasará desapercibido para la persona que llama. Modificar el objeto al que hace referencia la variable es un concepto completamente diferente.


Los valores primitivos también se definen en la Especificación de máquina virtual Java, aquí . El valor del tipo es el valor de punto flotante o integral correspondiente, codificado apropiadamente (8, 16, 32, 64, etc. bits).


42

En Java solo se pasan referencias y se pasan por valor:

Todos los argumentos de Java se pasan por valor (la referencia se copia cuando la usa el método):

En el caso de los tipos primitivos, el comportamiento de Java es simple: el valor se copia en otra instancia del tipo primitivo.

En el caso de los objetos, esto es lo mismo: las variables de objeto son punteros (cubos) que contienen solo la dirección del objeto que se creó con la palabra clave "nueva" y se copian como tipos primitivos.

El comportamiento puede parecer diferente de los tipos primitivos: porque la variable objeto copiada contiene la misma dirección (al mismo Objeto). El contenido / los miembros del Objeto aún podrían modificarse dentro de un método y luego acceder al exterior, dando la ilusión de que el Objeto (que contiene) se pasó por referencia.

Los objetos de "cadena" parecen ser un buen contraejemplo de la leyenda urbana que dice que "los objetos se pasan por referencia":

En efecto, utilizando un método, nunca podrá actualizar el valor de una Cadena pasada como argumento:

Un objeto de cadena contiene caracteres por una matriz declarada final que no puede modificarse. Solo la dirección del Objeto puede ser reemplazada por otra usando "nuevo". El uso de "nuevo" para actualizar la variable no permitirá que se acceda al Objeto desde afuera, ya que la variable se pasó inicialmente por valor y se copió.


Entonces, ¿es byRef en lo que respecta a los objetos y byVal en lo que respecta a las primitivas?
Mox

@mox, lea: los objetos no se pasan por referencia, esta es una leyenda: String a = new String ("sin cambios");
user1767316

1
@Aaron "Pasar por referencia" no significa "pasar un valor que es miembro de un tipo que se llama 'referencia' en Java". Los dos usos de "referencia" significan cosas diferentes.
philipxy

2
Respuesta bastante confusa. La razón por la que no puede cambiar un objeto de cadena en un método, que se pasa por referencia, es que los objetos de cadena son inmutables por diseño y no puede hacer cosas como estas strParam.setChar ( i, newValue ). Dicho esto, las cadenas, como cualquier otra cosa, se pasan por valor y, dado que String es un tipo no primitivo, ese valor es una referencia a lo que se creó con new, y puede verificar esto usando String.intern () .
zakmck

1
En cambio, no puede cambiar una cadena a través de param = "otra cadena" (equivalente a una nueva cadena ("otra cadena")) porque el valor de referencia de param (que ahora apunta a "otra cadena") no puede volver del método cuerpo. Pero eso es cierto para cualquier otro objeto, la diferencia es que, cuando la interfaz de clase lo permite, puede hacer param.changeMe () y el objeto de nivel superior al que se hace referencia cambiará porque param lo apunta, a pesar del valor de referencia de param (la ubicación direccionada, en términos de C) no puede aparecer desde el método.
zakmck

39

La distinción, o tal vez solo la forma en que recuerdo como solía estar bajo la misma impresión que el póster original, es esta: Java siempre se pasa por valor. Todos los objetos (en Java, cualquier cosa excepto las primitivas) en Java son referencias. Estas referencias se pasan por valor.


2
Su penúltima oración me parece muy engañosa. No es cierto que "todos los objetos en Java son referencias". Son solo las referencias a esos objetos las que son referencias.
Dawood ibn Kareem

37

Como muchas personas lo mencionaron antes, Java siempre es un valor de paso

Aquí hay otro ejemplo que lo ayudará a comprender la diferencia ( el ejemplo de intercambio clásico ):

public class Test {
  public static void main(String[] args) {
    Integer a = new Integer(2);
    Integer b = new Integer(3);
    System.out.println("Before: a = " + a + ", b = " + b);
    swap(a,b);
    System.out.println("After: a = " + a + ", b = " + b);
  }

  public static swap(Integer iA, Integer iB) {
    Integer tmp = iA;
    iA = iB;
    iB = tmp;
  }
}

Huellas dactilares:

Antes: a = 2, b = 3
Después: a = 2, b = 3

Esto sucede porque iA e iB son nuevas variables de referencia local que tienen el mismo valor de las referencias pasadas (apuntan a a y b respectivamente). Por lo tanto, tratar de cambiar las referencias de iA o iB solo cambiará en el ámbito local y no fuera de este método.


32

Java solo ha pasado por valor. Un ejemplo muy simple para validar esto.

public void test() {
    MyClass obj = null;
    init(obj);
    //After calling init method, obj still points to null
    //this is because obj is passed as value and not as reference.
}
private void init(MyClass objVar) {
    objVar = new MyClass();
}

44
Esta es la forma más clara y sencilla de ver que Java pasa por valor. El valor de obj( null) se pasó a init, no una referencia a obj.
David Schwartz

31

Siempre pienso en ello como "pasar por copia". Es una copia del valor, ya sea primitivo o de referencia. Si es una primitiva, es una copia de los bits que son el valor y si es un objeto, es una copia de la referencia.

public class PassByCopy{
    public static void changeName(Dog d){
        d.name = "Fido";
    }
    public static void main(String[] args){
        Dog d = new Dog("Maxx");
        System.out.println("name= "+ d.name);
        changeName(d);
        System.out.println("name= "+ d.name);
    }
}
class Dog{
    public String name;
    public Dog(String s){
        this.name = s;
    }
}

Salida de Java PassByCopy:

nombre = nombre Maxx
= Fido

Las clases de envoltura primitiva y las cadenas son inmutables, por lo que cualquier ejemplo que use esos tipos no funcionará igual que otros tipos / objetos.


55
"pase de copia" es lo que pasa por valor medio .
TJ Crowder

@TJCrowder. "Pasar por copia" puede ser una forma más apropiada de expresar el mismo concepto para fines Java: porque semánticamente hace que sea muy difícil calzar una idea similar a pasar por referencia, que es lo que quieres en Java.
Mike roedor

@mikerodent - Lo siento, no seguí eso. :-) "Pasar por valor" y "pasar por referencia" son los términos de arte correctos para estos conceptos. Deberíamos usarlos (proporcionando sus definiciones cuando la gente los necesite) en lugar de inventar nuevas. Anteriormente dije "pasar por copia" es lo que significa "pasar por valor", pero en realidad, no está claro qué significa "pasar por copia": ¿una copia de qué? ¿El valor? (Por ejemplo, referencia de objeto). ¿El objeto en sí? Así que me apego a los términos del arte. :-)
TJ Crowder

29

A diferencia de otros lenguajes, Java no le permite elegir entre pasar por valor y pasar por referencia: todos los argumentos se pasan por valor. Una llamada a un método puede pasar dos tipos de valores a un método: copias de valores primitivos (por ejemplo, valores de int y double) y copias de referencias a objetos.

Cuando un método modifica un parámetro de tipo primitivo, los cambios en el parámetro no tienen efecto sobre el valor del argumento original en el método de llamada.

Cuando se trata de objetos, los objetos en sí no se pueden pasar a los métodos. Entonces pasamos la referencia (dirección) del objeto. Podemos manipular el objeto original usando esta referencia.

Cómo Java crea y almacena objetos: cuando creamos un objeto, almacenamos la dirección del objeto en una variable de referencia. Analicemos la siguiente declaración.

Account account1 = new Account();

"Cuenta cuenta1" es el tipo y el nombre de la variable de referencia, "=" es el operador de asignación, "nuevo" solicita la cantidad de espacio requerida del sistema. El constructor a la derecha de la palabra clave new que crea el objeto se llama implícitamente por la palabra clave new. La dirección del objeto creado (resultado del valor correcto, que es una expresión llamada "expresión de creación de instancia de clase") se asigna al valor izquierdo (que es una variable de referencia con un nombre y un tipo especificado) utilizando el operador de asignación.

Aunque la referencia de un objeto se pasa por valor, un método aún puede interactuar con el objeto referenciado llamando a sus métodos públicos utilizando la copia de la referencia del objeto. Dado que la referencia almacenada en el parámetro es una copia de la referencia que se pasó como argumento, el parámetro en el método llamado y el argumento en el método de llamada se refieren al mismo objeto en la memoria.

Pasar referencias a matrices, en lugar de los objetos de la matriz en sí, tiene sentido por razones de rendimiento. Debido a que todo en Java se pasa por valor, si se pasan objetos de matriz, se pasaría una copia de cada elemento. Para matrices grandes, esto perdería tiempo y consumiría un almacenamiento considerable para las copias de los elementos.

En la imagen a continuación, puede ver que tenemos dos variables de referencia (estas se llaman punteros en C / C ++, y creo que ese término facilita la comprensión de esta característica) en el método principal. Las variables primitivas y de referencia se guardan en la memoria de la pila (lado izquierdo en las imágenes a continuación). "punto" de las variables de referencia array1 y array2 (como lo llaman los programadores de C / C ++) o referencia a las matrices ayb respectivamente, que son objetos (los valores que contienen estas variables de referencia son direcciones de objetos) en la memoria del montón (lado derecho en las imágenes a continuación) .

Pase por valor ejemplo 1

Si pasamos el valor de la variable de referencia array1 como argumento al método reverseArray, se crea una variable de referencia en el método y esa variable de referencia comienza a apuntar a la misma matriz (a).

public class Test
{
    public static void reverseArray(int[] array1)
    {
        // ...
    }

    public static void main(String[] args)
    {
        int[] array1 = { 1, 10, -7 };
        int[] array2 = { 5, -190, 0 };

        reverseArray(array1);
    }
}

Pase por valor ejemplo 2

Entonces, si decimos

array1[0] = 5;

en el método reverseArray, hará un cambio en la matriz a.

Tenemos otra variable de referencia en el método reverseArray (array2) que apunta a una matriz c. Si tuviéramos que decir

array1 = array2;

en el método reverseArray, entonces la variable de referencia array1 en el método reverseArray dejaría de apuntar a la matriz a y comenzaría a apuntar a la matriz c (Línea de puntos en la segunda imagen).

Si devolvemos el valor de la variable de referencia array2 como el valor de retorno del método reverseArray y asignamos este valor a la variable de referencia array1 en el método principal, array1 en main comenzará a apuntar a la matriz c.

Así que escribamos todas las cosas que hemos hecho a la vez.

public class Test
{
    public static int[] reverseArray(int[] array1)
    {
        int[] array2 = { -7, 0, -1 };

        array1[0] = 5; // array a becomes 5, 10, -7

        array1 = array2; /* array1 of reverseArray starts
          pointing to c instead of a (not shown in image below) */
        return array2;
    }

    public static void main(String[] args)
    {
        int[] array1 = { 1, 10, -7 };
        int[] array2 = { 5, -190, 0 };

        array1 = reverseArray(array1); /* array1 of 
         main starts pointing to c instead of a */
    }
}

ingrese la descripción de la imagen aquí

Y ahora que el método reverseArray ha terminado, sus variables de referencia (array1 y array2) se han ido. Lo que significa que ahora solo tenemos las dos variables de referencia en el método principal matriz1 y matriz2 que apuntan a las matrices c y b respectivamente. Ninguna variable de referencia apunta al objeto (matriz) a. Por lo tanto, es elegible para la recolección de basura.

También puede asignar el valor de array2 en main a array1. array1 comenzaría a apuntar a b.


27

He creado un hilo dedicado a este tipo de preguntas para cualquier lenguaje de programación aquí .

También se menciona Java . Aquí está el breve resumen:

  • Java le pasa parámetros por valor
  • "por valor" es la única forma en Java de pasar un parámetro a un método
  • El uso de métodos del objeto dado como parámetro alterará el objeto a medida que las referencias apuntan a los objetos originales. (si ese método en sí altera algunos valores)

27

Para resumir, los objetos Java tienen algunas propiedades muy peculiares.

En general, Java tiene tipos primitivos ( int, bool, char, double, etc.) que se transmiten directamente por valor. Entonces Java tiene objetos (todo lo que se deriva de java.lang.Object). Los objetos siempre se manejan a través de una referencia (una referencia es un puntero que no puede tocar). Eso significa que, en efecto, los objetos se pasan por referencia, ya que las referencias normalmente no son interesantes. Sin embargo, significa que no puede cambiar a qué objeto se apunta cuando la referencia en sí misma se pasa por valor.

¿Suena extraño y confuso? Consideremos cómo C implementa pasa por referencia y pasa por valor. En C, la convención predeterminada es pasar por valor. void foo(int x)pasa un int por valor. void foo(int *x)es una función que no quiere una int a, sino un puntero a un int: foo(&a). Uno usaría esto con el &operador para pasar una dirección variable.

Lleva esto a C ++, y tenemos referencias. Las referencias son básicamente (en este contexto) azúcar sintáctico que oculta la parte del puntero de la ecuación: void foo(int &x)se llama por foo(a), donde el compilador mismo sabe que es una referencia y se adebe pasar la dirección de la no referencia . En Java, todas las variables que se refieren a objetos son en realidad de tipo de referencia, en efecto forzando la llamada por referencia para la mayoría de las intenciones y propósitos sin el control (y la complejidad) de grano fino que ofrece, por ejemplo, C ++.


Creo que está muy cerca de mi comprensión del objeto Java y su referencia. El objeto en Java se pasa a un método mediante una copia de referencia (o alias).
MaxZoom
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.