pass parameter change java pass-by-reference pass-by-value

parameter - java pass object by reference



¿Puede alguien explicarme cuál es el razonamiento detrás de pasar por "valor" y no por "referencia" en Java? (15)

Soy bastante nuevo en Java (he estado escribiendo otras cosas durante muchos años) y, a menos que me esté perdiendo algo (y estoy feliz de estar equivocado aquí), el siguiente es un error fatal ...

String foo = new String(); thisDoesntWork(foo); System.out.println(foo);//this prints nothing public static void thisDoesntWork(String foo){ foo = "howdy"; }

Ahora, soy muy consciente del concepto (bastante pobremente redactado) de que en Java todo pasa por "valor" y no como "referencia", pero String es un objeto y tiene toda clase de campanas y silbatos, por lo que cabría esperar que a diferencia de un int, un usuario podría operar sobre lo que pasó en el método (y no quedar atrapado con el valor establecido por el = sobrecargado).

¿Puede alguien explicarme cuál fue el razonamiento detrás de esta elección de diseño? Como dije, no estoy buscando estar aquí, ¿y quizás me esté perdiendo algo obvio?


¿Estás seguro de que imprime nulo? Creo que estará en blanco, ya que cuando inicializaste la variable foo que proporcionaste String vacía.

La asignación de foo en este método DoesntWork no está cambiando la referencia de la variable foo definida en clase, por lo que foo en System.out.println (foo) seguirá apuntando al antiguo objeto de cadena vacío.


@Axelle

Compañero, ¿realmente sabes la diferencia entre pasar por valor y por referencia?

En Java, incluso las referencias se pasan por valor. Cuando pasa una referencia a un objeto, obtiene una copia del puntero de referencia en la segunda variable. Tahts por qué la segunda variable se puede cambiar sin afectar la primera.


Como mi respuesta original fue "¿Por qué sucedió?" Y no "¿Por qué se diseñó el lenguaje?", Le daré otra oportunidad.

Para simplificar las cosas, me desharé de la llamada al método y mostraré lo que está sucediendo de otra manera.

String a = "hello"; String b = a; String b = "howdy" System.out.print(a) //prints hello

Para obtener la última instrucción para imprimir "hola", b tiene que apuntar al mismo "agujero" en la memoria que apunta a (un puntero). Esto es lo que quiere cuando quiere pasar por referencia. Hay un par de razones por las que Java decidió no ir en esta dirección:

  • Los indicadores son confusos Los diseñadores de Java intentaron eliminar algunas de las cosas más confusas sobre otros lenguajes. Los punteros son una de las construcciones mal interpretadas y mal utilizadas de C / C ++ junto con la sobrecarga del operador.

  • Los indicadores son riesgos de seguridad Los indicadores provocan muchos problemas de seguridad cuando se usan incorrectamente. Un programa malicioso asigna algo a esa parte de la memoria, entonces, lo que pensaste que era tu objeto es realmente de otra persona. (Java ya se deshizo del mayor problema de seguridad, desbordamientos de búfer, con arreglos comprobados)

  • Fuga de abstracción Cuando empiezas a ocuparte exactamente de "Lo que está en la memoria y dónde", tu abstracción se vuelve menos abstracción. Mientras que la fuga de abstracción casi con certeza se cuela en un lenguaje, los diseñadores no quisieron cocerlo directamente.

  • Los objetos son lo único que importa En Java, todo es un objeto, no el espacio que ocupa un objeto. Agregar punteros haría que el espacio que ocupa un objeto sea importante, aunque .......

Puedes emular lo que quieras creando un objeto "Agujero". Incluso podría usar genéricos para que sea seguro. Por ejemplo:

public class Hole<T> { private T objectInHole; public void putInHole(T object) { this.objectInHole = object; } public T getOutOfHole() { return objectInHole; } public String toString() { return objectInHole.toString(); } .....equals, hashCode, etc. } Hole<String> foo = new Hole<String)(); foo.putInHole(new String()); System.out.println(foo); //this prints nothing thisWorks(foo); System.out.println(foo);//this prints howdy public static void thisWorks(Hole<String> foo){ foo.putInHole("howdy"); }


Cuando pasa "foo", está pasando la referencia a "foo" como un valor a ThisDoesntWork (). Eso significa que cuando haces la tarea de "foo" dentro de tu método, simplemente estás configurando una referencia de variable local (foo) para que sea una referencia a tu nueva cadena.

Otra cosa a tener en cuenta cuando se piensa en cómo se comportan las cadenas en Java es que las cadenas son inmutables. Funciona de la misma manera en C #, y por algunas buenas razones:

  • Seguridad : ¡Nadie puede atascar datos en su cadena y causar un error de desbordamiento del búfer si nadie puede modificarlo!
  • Velocidad : si puede estar seguro de que sus cadenas son inmutables, sabrá que su tamaño es siempre el mismo y que nunca tendrá que mover la estructura de datos en la memoria cuando la manipule. Usted (el diseñador del lenguaje) tampoco tiene que preocuparse por implementar el String como una lista enlazada lenta. Esto corta en ambos sentidos, sin embargo. Agregar cadenas simplemente usando el operador + puede ser costoso en cuanto a la memoria, y deberá usar un objeto StringBuilder para hacer esto de una manera eficiente y de alto rendimiento.

Ahora en tu pregunta más grande. ¿Por qué los objetos pasan de esta manera? Bueno, si Java pasó su cadena como lo que tradicionalmente llamaría "por valor", tendría que copiar toda la cadena antes de pasarla a su función. Eso es bastante lento. Si pasó la cadena por referencia y te permite cambiarla (como hace C), tendrías los problemas que acabo de enumerar.


Dave, tienes que perdonarme (bueno, supongo que no "tienes que hacerlo", pero preferiría que lo hicieras), pero esa explicación no es demasiado convincente. Las ganancias de Seguridad son bastante mínimas ya que cualquier persona que necesite cambiar el valor de la cadena encontrará la manera de hacerlo con alguna solución desagradable. Y la velocidad ?! Usted mismo (bastante correctamente) afirma que todo el negocio con el + es extremadamente caro.

El resto de ustedes, por favor comprendan que ME GUSTA cómo funciona, estoy preguntando POR QUÉ funciona de esa manera ... por favor dejen de explicar la diferencia entre las metodologías.

(y honestamente no estoy buscando ningún tipo de pelea aquí, por cierto, simplemente no veo cómo esta fue una decisión racional).


El problema es que estás instanciando un tipo de referencia de Java. Luego pasa ese tipo de referencia a un método estático y lo reasigna a una variable de ámbito local.

No tiene nada que ver con la inmutabilidad. Exactamente lo mismo habría sucedido para un tipo de referencia mutable.


En java, todas las variables aprobadas son pasadas por valores, incluso por objetos. Todas las variables pasadas a un método son en realidad copias del valor original. En el caso de su ejemplo de cadena, el puntero original (en realidad es una referencia, pero para evitar confusión use una palabra diferente) se copia en una nueva variable que se convierte en el parámetro del método.

Sería un dolor si todo fuera por referencia. Uno necesitaría hacer copias privadas por todo el lugar, lo que definitivamente sería un verdadero dolor. Todo el mundo sabe que el uso de la inmutabilidad para tipos de valor, etc. hace que sus programas sean infinitamente más simples y escalables.

Algunos beneficios incluyen: - No es necesario hacer copias defensivas. - Threadsafe: no hay necesidad de preocuparse por el bloqueo por si acaso alguien más quiere cambiar el objeto.


Es porque crea una variable local dentro del método. lo que sería una manera fácil (de la cual estoy bastante seguro funcionaría) sería:

String foo = new String(); thisDoesntWork(foo); System.out.println(foo); //this prints nothing public static void thisDoesntWork(String foo) { this.foo = foo; //this makes the local variable go to the main variable foo = "howdy"; }


Esto se debe a que dentro de "thisDoesntWork" está destruyendo efectivamente el valor local de foo. Si desea pasar por referencia de esta manera, siempre puede encapsular la Cadena dentro de otro objeto, por ejemplo, en una matriz.

class Test { public static void main(String[] args) { String [] fooArray = new String[1]; fooArray[0] = new String("foo"); System.out.println("main: " + fooArray[0]); thisWorks(fooArray); System.out.println("main: " + fooArray[0]); } public static void thisWorks(String [] foo){ System.out.println("thisWorks: " + foo[0]); foo[0] = "howdy"; System.out.println("thisWorks: " + foo[0]); } }

Resultados en el siguiente resultado:

main: foo thisWorks: foo thisWorks: howdy main: howdy


Si considera un objeto como solo los campos en el objeto, entonces los objetos se pasan por referencia en Java porque un método puede modificar los campos de un parámetro y un llamante puede observar la modificación. Sin embargo, si también piensas en un objeto como su identidad, entonces los objetos se pasan por valor porque un método no puede cambiar la identidad de un parámetro de forma tal que la persona que llama pueda observar. Entonces yo diría que Java es pass-by-value.


Si hiciéramos una analogía aproximada de C y ensamblador:

void Main() { // stack memory address of message is 0x8001. memory address of Hello is 0x0001. string message = "Hello"; // assembly equivalent of: message = "Hello"; // [0x8001] = 0x0001 // message''s stack memory address printf("%d", &message); // 0x8001 printf("%d", message); // memory pointed to of message(0x8001): 0x0001 PassStringByValue(message); // pass the pointer pointed to of message. 0x0001, not 0x8001 printf("%d", message); // memory pointed to of message(0x8001): 0x0001. still the same // message''s stack memory address doesn''t change printf("%d", &message); // 0x8001 } void PassStringByValue(string foo) { printf("%d", &foo); // &foo contains foo''s *stack* address (0x4001) // foo(0x4001) contains the memory pointed to of message, 0x0001 printf("%d", foo); // 0x0001 // World is in memory address 0x0002 foo = "World"; // on foo''s memory address (0x4001), change the memory it pointed to, 0x0002 // assembly equivalent of: foo = "World": // [0x4001] = 0x0002 // print the new memory pointed by foo printf("%d", foo); // 0x0002 // Conclusion: Not in any way 0x8001 was involved in this function. Hence you cannot change the Main''s message value. // foo = "World" is same as [0x4001] = 0x0002 }

void Main() { // stack memory address of message is 0x8001. memory address of Hello is 0x0001. string message = "Hello"; // assembly equivalent of: message = "Hello"; // [0x8001] = 0x0001 // message''s stack memory address printf("%d", &message); // 0x8001 printf("%d", message); // memory pointed to of message(0x8001): 0x0001 PassStringByRef(ref message); // pass the stack memory address of message. 0x8001, not 0x0001 printf("%d", message); // memory pointed to of message(0x8001): 0x0002. was changed // message''s stack memory address doesn''t change printf("%d", &message); // 0x8001 } void PassStringByRef(ref string foo) { printf("%d", &foo); // &foo contains foo''s *stack* address (0x4001) // foo(0x4001) contains the address of message(0x8001) printf("%d", foo); // 0x8001 // World is in memory address 0x0002 foo = "World"; // on message''s memory address (0x8001), change the memory it pointed to, 0x0002 // assembly equivalent of: foo = "World": // [0x8001] = 0x0002; // print the new memory pointed to of message printf("%d", foo); // 0x0002 // Conclusion: 0x8001 was involved in this function. Hence you can change the Main''s message value. // foo = "World" is same as [0x8001] = 0x0002 }

Una posible razón por la cual todo se pasa por valor en Java, su gente de diseño de idiomas quiere simplificar el lenguaje y hacer todo en forma de OOP.

Prefieren que diseñe un intercambiador de enteros usando objetos que ellos que proporcionan un soporte de primera clase para el paso de referencia, el mismo para el delegado (Gosling se siente repulsivo con el puntero a la función, preferiría incluir esa funcionalidad en los objetos) y enum.

Simplifican demasiado (todo es un objeto) el lenguaje en detrimento de no tener soporte de primera clase para la mayoría de los constructos del lenguaje, por ejemplo, pasar por referencia, delegados, enum, propiedades viene a la mente.


Su pregunta tal como se le preguntó en realidad no tiene que ver con pasar por valor, pasar por referencia, o por el hecho de que las cadenas son inmutables (como otros lo han expresado).

Dentro del método, realmente creas una variable local (la llamaré "localFoo") que apunta a la misma referencia que tu variable original ("originalFoo").

Cuando asigna "howdy" a localFoo, no cambia a dónde apunta originalFooo.

Si hiciste algo como:

String a = ""; String b = a; String b = "howdy"?

¿Esperarías?

System.out.print(a)

para imprimir "howdy"? Se imprime "".

No puede cambiar a lo que apunta OriginalFoo cambiando lo que apunta localFoo. Puede modificar el objeto al que ambos apuntan (si no fue inmutable). Por ejemplo,

List foo = new ArrayList(); System.out.println(foo.size());//this prints 0 thisDoesntWork(foo); System.out.println(foo.size());//this prints 1 public static void thisDoesntWork(List foo){ foo.add(new Object); }


Ve a hacer el gran tutorial en el sitio web de soles.

Pareces no entender la diferencia que las variables de alcance pueden ser. "foo" es local para su método. Nada fuera de ese método puede cambiar lo que "foo" señala también. El "foo" que se refiere a su método es un campo completamente diferente, es un campo estático en su clase adjunta.

El alcance es especialmente importante ya que no quiere que todo sea visible para todo lo demás en su sistema.


Los argumentos de referencia se pasan como referencias a los objetos mismos (no a otras variables que hacen referencia a objetos). Puede llamar a métodos en el objeto que se ha pasado. Sin embargo, en tu muestra de código:

public static void thisDoesntWork(String foo){ foo = "howdy"; }

solo está almacenando una referencia a la cadena "howdy" en una variable que es local al método. Esa variable local ( foo ) se inicializó al valor del foo del llamante cuando se llamó al método, pero no tiene referencia a la variable de la persona que llama. Después de la inicialización:

caller data method ------ ------ ------ (foo) --> "" <-- (foo)

Después de la asignación en tu método:

caller data method ------ ------ ------ (foo) --> "" "hello" <-- (foo)

Tiene otros problemas allí: las instancias de String son inmutables (por diseño, por seguridad) por lo que no puede modificar su valor.

Si realmente desea que su método proporcione un valor inicial para su cadena (o en cualquier momento de su vida, para el caso), haga que su método devuelva un valor de String que asigne a la variable de la persona que llama en el momento de la llamada. Algo como esto, por ejemplo:

String foo = thisWorks(); System.out.println(foo);//this prints the value assigned to foo in initialization public static String thisWorks(){ return "howdy"; }


Esta diatriba lo explica mejor de lo que yo podría intentar:

En Java, las primitivas se pasan por valor. Sin embargo, los objetos no se pasan por referencia. Una declaración correcta sería Referencias de objetos pasadas por valor.