methods parametros - ¿Es Java "paso por referencia" o "paso por valor"?



de ejemplo (25)

Siempre pensé que Java era paso por referencia .

Sin embargo, he visto un par de publicaciones de 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?


Answers

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 de 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 su lugar, 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 las respuestas anteriores, en Java está pasando un puntero a la matriz como un valor en getValues. Eso es suficiente, porque el método modifica el elemento de la matriz y, por convención, se 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.


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

Entonces, ¿cómo es posible que alguien se confunda con esto y crea que Java se pasa por referencia, o cree que tiene un ejemplo de que Java actúa como pase por referencia? El punto clave es que Java nunca proporciona acceso directo a los valores de los objetos en cualquier 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 los campos y variables y los argumentos de los métodos como objetos , cuando de forma pedante son solo referencias a objetos . La confusión se deriva de este cambio (estrictamente hablando, incorrecto) en la nomenclatura.

Entonces, al llamar 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 en la práctica es indistinguible) pasar un objeto por referencia.


Como mucha gente lo mencionó antes, Java siempre es pasado por valor

Aquí hay otro ejemplo que le 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 y iB son nuevas variables de referencia local que tienen el mismo valor de las referencias pasadas (apuntan a y b respectivamente). Por lo tanto, intentar cambiar las referencias de iA o iB solo cambiará en el ámbito local y no fuera de este método.


Java siempre es pasar-por-valor . Desafortunadamente, decidieron llamar a la ubicación de un objeto una "referencia". Cuando pasamos el valor de un objeto, le estamos pasando la referencia . Esto es confuso para los principiantes.

Dice así:

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

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

En el ejemplo anterior, aDog.getName() aún devolverá "Max" . El valor aDog dentro de main no se cambia en la función foo con Dog "Fifi" ya que la referencia del objeto se pasa por valor. Si se pasara por referencia, aDog.getName() en main devolvería "Fifi" después de la llamada a foo .

Igualmente:

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

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

En el ejemplo anterior, Fifi es el nombre del perro después de la llamada a foo(aDog) porque el nombre del objeto se estableció dentro de foo(...) . Todas las operaciones que foo realiza en d son tales que, para todos los propósitos prácticos, se realizan en aDog (excepto cuando d se cambia para apuntar a una instancia de Dog diferente como d = new Dog("Boxer") ).


Una referencia siempre es un valor cuando se representa, independientemente del idioma que utilice.

Al obtener una vista externa de la caja, veamos Ensamblado o alguna administración de memoria de bajo nivel. En el nivel de la 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 el 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 Ensamblaje tenemos una solución conveniente para dar un Nombre a cierta Ubicación (también conocida como variable), pero al compilar el código, el ensamblador simplemente reemplaza Nombre por la ubicación designada al igual que su navegador reemplaza los nombres de dominio con direcciones IP.

Básicamente, es técnicamente imposible pasar una referencia a cualquier cosa en cualquier idioma sin representarlo (cuando se convierte de inmediato 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 223er 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 observa 5 y 47 sin ninguna otra información, verá solo dos valores . Si los usas como referencias entonces 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 varios modos de invocación de método:

  1. 5 se copia en uno de los registros de la CPU (es decir, EAX).
  2. 5 obtiene PUSHd a la pila.
  3. 47 se copia en uno de los registros de la CPU
  4. 47 PUSHd 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, se ha creado una copia de un valor existente, ahora depende del método de recepción para manejarlo. Cuando se escribe "Foo" dentro del método, se lee en EAX, o se elimina la referencia automáticamente o se hace doble referencia, el proceso depende de cómo funciona el idioma y / o de lo que dicta el tipo de Foo. Esto se oculta al desarrollador hasta que ella 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 idioma).

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 dentro del método, ni siquiera podemos determinar dónde se ubicó el Foo original en la memoria.
  • en el caso 3. y 4. si usa construcciones de idioma predeterminadas y cambia Foo ( Foo = 11), podría cambiar Foo globalmente (depende del idioma, es decir, Java o como la procedure findMin(x, y, z: integer; var m de Pascal : integer);). Sin embargo, si el lenguaje le permite eludir el proceso de desreferencia, puede cambiar 47, digamos a 49. En ese punto, parece que Foo ha 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 ejecutables El programa y la escritura en él modificarán el código en 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, que apunta a un valor ahora malo, que se usa nuevamente como un puntero) pero esto sigue siendo un local Problema, ya que se copió el 223 . Sin embargo, si puede desreferenciar Ref2Foo(es decir 223), alcanzar y modificar el valor señalado 47, digamos, 49afectará a Foo globalmente , porque en este caso los métodos obtuvieron una copia 223pero la referencia 47solo existe una vez, y cambiarla que 49conducirá cada Ref2Foodoble desreferencia a un valor incorrecto.

Al optar por detalles insignificantes, incluso los idiomas que pasan por referencia pasarán valores a las funciones, pero esas funciones saben que tienen que usarlo para fines de desreferenciación. Este paso-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 por valor estricto también es inútil, significaría que una matriz de 100 Mbyte debería copiarse cada vez que llamamos a un método con la matriz como argumento, por lo tanto, Java no puede ser estrictamente pasado por valor. Cada idioma pasaría una referencia a esta gran matriz (como un valor) y empleará un mecanismo de copia en escritura si esa matriz puede cambiarse localmente dentro del método o permite que el método (como lo hace Java) modifique la matriz globalmente (desde la vista de la persona que llama) y algunos idiomas permiten modificar el valor de la referencia en sí.

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


Para hacer una historia larga, los objetos Java tienen algunas propiedades muy peculiares.

En general, Java tiene tipos primitivos ( int, bool, char, double, etc) que se pasan directamente por valor. Entonces Java tiene objetos (todo lo que deriva de java.lang.Object). En realidad, 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, ya que la referencia en sí misma se pasa por valor.

¿Esto suena extraño y confuso? Consideremos cómo C implementa pasar por referencia y pasar 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áctica que oculta la parte del puntero de la ecuación: void foo(int &x)es llamada por foo(a), donde el compilador mismo sabe que es una referencia y la dirección de la no referencia adebe ser pasada. En Java, todas las variables que se refieren a objetos son en realidad de tipo de referencia, lo que en realidad obliga a llamar por referencia para la mayoría de los propósitos y propósitos sin el control detallado (y la complejidad) que ofrece, por ejemplo, C ++.


Siempre lo veo 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 = Maxx
nombre = Fido

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


Permítame 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 'Pasar por valor tiene la sensación de' Pasar por referencia '

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

Pero a partir de este ejemplo, podemos entender que se trata de pasar de hecho solo 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 son capaces de cambiar y aún se mantiene vigente 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 que se mencionó en el Ejemplo 3 (PassByValueObjectCase1.java), no podemos cambiar la referencia real fuera del alcance original ".

Nota: No estoy pegando el código para private class Student. La definición de clase para Studentes igual que en Ejemplo3.

* /

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]

Me acabo de dar cuenta de que referenciaste mi artículo

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

La clave para entender esto es que algo 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ás pasando la dirección del objeto Dog creado al método foo .

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

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

si el método fuera definido 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á pasando.

  • El parámetro someDog se establece en el valor 42
  • en la línea "AAA"
    • someDog le sigue el Dog que apunta (el objeto Dog en la dirección 42)
    • se le pide a ese Dog (el de la dirección 42) que cambie su nombre a Max
  • en la línea "BBB"
    • Se crea un nuevo Dog . Digamos que está en la dirección 74
    • Le asignamos el parámetro someDog a 74
  • en la línea "CCC"
    • A algún perro le sigue el Dog que apunta (el objeto Dog en la dirección 74)
    • a ese Dog (el que está en la dirección 74) se le pide que cambie su nombre a Rowlf
  • luego volvemos

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

¿ myDog ?

Ahí está la clave.

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

Es perfectamente válido seguir una dirección y cambiar lo que hay 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 a los que se apuntó. Sin embargo, no puede cambiar a donde apunta ese puntero.

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

Si Java tuviera una semántica de paso por referencia, el método foo que definimos anteriormente habría cambiado donde myDog estaba apuntando cuando asignó someDog en la línea BBB.

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


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

El primer paso borra de tu mente la 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.

El segundo paso 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 indican 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 así: 3bad086a. 3bad086a representa una forma de llegar al objeto pasado.
  3. Así que solo estás pasando 3bad086a que es el valor de la referencia.
  4. Está pasando el valor de la referencia y no la referencia en sí (y no el objeto).
  5. Este valor es en realidad COPIADO y dado al método .

En lo siguiente (por favor 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 # 1 y es nula al principio.
  • Se crea un nuevo objeto de persona en la línea # 2, se almacena en la memoria, y la persona variable recibe la referencia al objeto de persona. Es decir, su dirección. Digamos 3bad086a.
  • La persona variable que contiene 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.
  • Compruebe 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 variable / persona de referencia se copia bit a bit y se pasa a otroReferenceToTheSamePersonObject dentro de la función.
    • No se crean nuevas instancias de Person.
    • Tanto " person " como " anotherReferenceToTheSamePersonObject " tienen el mismo valor de 3bad086a.
    • No intente esto, pero la persona == anotherReferenceToTheSamePersonObject sería verdadera.
    • Ambas variables tienen COPIAS IDENTICAS de la referencia y ambas se refieren al mismo Objeto de persona, el MISMO objeto en el montón y NO A UNA COPIA.

Una imagen vale mas que mil palabras:

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

Si no lo obtuvo, entonces confíe en mí y recuerde que es mejor decir que Java se pasa por valor . Bueno, pasar por valor de referencia . Oh, bueno, ¡aún mejor es pasar-por-la-copia-del-valor-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 se 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 en sí.
  • Si se trata de 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 pasado por valor porque dentro de un método puede modificar el Objeto referenciado tanto como quiera, 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 de cambio de nombre 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 Objeto.

¡Por supuesto que puede abreviar y simplemente decir que Java se pasa de valor!


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

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

  • Java pasa los 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 ya que las referencias apuntan a los objetos originales. (Si ese método sí altera algunos valores)

Por lo que sé, Java solo sabe 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 algunos escollos; 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 usas copys que no tienen impacto en las referencias en la página principal. Pero si tus objetos no son inmutables, puedes cambiarlos, 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 cambias StringBuffer en String, solo se producirá 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ías hacer una envoltura para String como esta 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);
}

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


No, no es pasar 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ámetros 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 se puede usar como un nombre simple en el cuerpo del método o constructor para referirse al parámetro formal .


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

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

Java no pasa argumentos de método por referencia; Los pasa por valor. Usaré el ejemplo 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);

Creando dos objetos Point diferentes con dos referencias diferentes asociadas.

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

Como se espera la salida será:

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

En esta línea 'pass-by-value' entra en juego ...

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

Las referencias pnt1y pnt2se pasan por valor al método complicado, lo que significa que ahora sus referencias pnt1y pnt2tienen su copiesnombre arg1y arg2.So pnt1y arg1 apunta al mismo objeto. (Lo mismo para el pnt2y arg2)

En el trickymétodo:

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

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 mueves la referencia arg1para apuntar al mismo lugar como arg2referencia. Finalmente arg2se apunte al mismo lugar como temp.

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

Entonces, después de ejecutar el método tricky, cuando regresas a main, tienes esta situación:

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

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.

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


Algunas correcciones a algunos posts.

C no admite el paso por referencia. SIEMPRE se pasa por valor. C ++ admite el paso por referencia, pero no es el valor predeterminado y es bastante peligroso.

No importa cuál sea el valor en Java: primitivo o dirección (aproximadamente) del objeto, SIEMPRE se pasa por valor.

Si un objeto Java "se comporta" como si se pasara por referencia, eso es una propiedad de mutabilidad y no tiene absolutamente nada que ver con los mecanismos de paso.

No estoy seguro de por qué esto es tan confuso, quizás porque muchos "programadores" de Java no están formalmente entrenados y, por lo tanto, no entienden lo que realmente está pasando en la memoria


Java siempre se pasa por valor, no por referencia

En primer lugar, debemos entender qué pasa por valor y pasa 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 .

Paso por referencia (también llamado paso 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).

new PassByValue().changeValue(t);

Al pasar la referencia t a la función, no pasará directamente el valor de referencia real de la prueba del objeto, pero creará una copia de t y luego la pasará a la función. Dado que 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 tanto, apuntarán al mismo objeto.

Si cambia algo en la función usando la referencia f, modificará el contenido existente del objeto. Es por eso que obtuvimos la salida 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 lanzará 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:

Esperemos que esto ayude.


Java siempre pasa argumentos por valor NO por referencia.

Déjame explicarte esto a través de un example :

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");
     }
}

Voy a explicar esto en pasos:

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

    Foo f = new Foo("f");
    

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

    public static void changeReference(Foo a)
    

  3. Al llamar al método changeReference , la referencia a se asignará al objeto que se pasa como argumento.

    changeReference(f);
    

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

    Foo b = new Foo("b");
    

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

  6. Al llamar al modifyReference(Foo c) , se crea una referencia c y se asigna al objeto con el atributo "f" .

  7. c.setAttribute("c"); cambiará el atributo del objeto que la referencia c apunta a él, y es el mismo objeto que la referencia f apunta a él.

Espero que entiendas ahora cómo funcionan los objetos pasados ​​como argumentos en Java :)


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


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

En C ++: Nota: Código incorrecto - ¡Fugas 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 transferencia: por valor para los tipos incorporados y por valor del puntero para los tipos de objeto.


Java pasa los parámetros por VALOR, y por valor SOLAMENTE .

Para acortar el cuento largo:

Para aquellos que vienen de C #: NO HAY NINGÚN parámetro "out".

Para aquellos que vienen de PASCAL: NO HAY "var" parámetro .

Esto significa que no puede cambiar la referencia desde el objeto en sí, pero siempre puede cambiar las propiedades del objeto.

Una solución es utilizar el StringBuilderparámetro en su lugar String. ¡Y siempre puedes usar arreglos!


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

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

Desplácese en la pila / montón antes de llegar a la implementación de Java: los valores se activan y desactivan de la pila de 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 aleatoria y desorganizada. La JVM solo encuentra espacio donde puede, y lo libera ya que las variables que lo utilizan ya no son necesarias.

Bueno. En primer lugar, los primitivos locales van a la pila. Así que este código:

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

resultados en esto:

Cuando declara y crea una instancia de un objeto. El objeto real va en el montón. ¿Qué pasa en la pila? La dirección del objeto en el montón. Los programadores de C ++ lo llamarían puntero, pero algunos desarrolladores de Java están en contra de la palabra "puntero". Lo que sea. Solo debes saber que la dirección del objeto va en la pila.

Al igual que:

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

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");

Entonces, ¿qué pasa cuando se llama 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 proponentes de "referencia" y "valor". Lo que llama no es tan importante como para comprender 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);
}

One String se crea y se le asigna un espacio 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 ningún nuevo espacio de almacenamiento dinámico, sino que se crea un nuevo identificador en la pila. Luego llamamos shout() : se crea un nuevo marco de pila y un nuevo identificador, se crea un name y se asigna la dirección de la Cadena ya existente.

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


Java solo tiene pase 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();
}

Java pasa referencias por valor.

Así que no puedes cambiar la referencia que se pasa.


Por cierto, ten en cuenta que si la cadena es nula, la llamada:

int i = Integer.parseInt(null);

lanza la excepción NumberFormatException, no NullPointerException.





java methods parameter-passing pass-by-reference pass-by-value