Cómo hacer el equivalente de pasar por referencia para primitivas en Java

116

Este código Java:

public class XYZ {   
    public static void main(){  
        int toyNumber = 5;   
        XYZ temp = new XYZ();  
        temp.play(toyNumber);  
        System.out.println("Toy number in main " + toyNumber);  
    }

    void play(int toyNumber){  
        System.out.println("Toy number in play " + toyNumber);   
        toyNumber++;  
        System.out.println("Toy number in play after increement " + toyNumber);   
    }   
}  

generará esto:

 
Número de juguete en juego 5  
Número de juguete en juego después del incremento 6  
Número de juguete en el principal 5  

En C ++ puedo pasar la toyNumbervariable como pasar por referencia para evitar el sombreado, es decir, crear una copia de la misma variable que se muestra a continuación:

void main(){  
    int toyNumber = 5;  
    play(toyNumber);  
    cout << "Toy number in main " << toyNumber << endl;  
}

void play(int &toyNumber){  
    cout << "Toy number in play " << toyNumber << endl;   
    toyNumber++;  
    cout << "Toy number in play after increement " << toyNumber << endl;   
} 

y la salida de C ++ será esta:

Número de juguete en juego 5  
Número de juguete en juego después del incremento 6  
Número de juguete principal 6  

Mi pregunta es: ¿cuál es el código equivalente en Java para obtener el mismo resultado que el código C ++, dado que Java se pasa por valor en lugar de pasar por referencia ?

Estudiante
fuente
22
Esto no me parece un duplicado: la pregunta supuestamente duplicada trata sobre cómo funciona Java y qué significan los términos, que es educación, mientras que esta pregunta pregunta específicamente cómo obtener algo que se parezca al comportamiento de pasar por referencia, algo que muchos C Los programadores de / C ++ / D / Ada podrían preguntarse cómo hacer un trabajo práctico, sin importarles por qué Java es todo paso por valor.
DarenW
1
@DarenW Estoy totalmente de acuerdo, he votado para reabrir. Ah, y tienes suficiente reputación para hacer lo mismo :-)
Duncan Jones
La discusión sobre las primitivas es bastante engañosa, ya que la pregunta se aplica igualmente a los valores de referencia.
shmosel
"En C ++ puedo pasar la variable toyNumber como pasada por referencia para evitar el sombreado" ; esto no es un sombreado porque la toyNumbervariable declarada en el mainmétodo no está dentro del alcance del playmétodo. El sombreado en C ++ y Java solo ocurre cuando hay anidación de ámbitos. Consulte en.wikipedia.org/wiki/Variable_shadowing .
Stephen C

Respuestas:

171

Tienes varias opciones. El que tenga más sentido realmente depende de lo que intente hacer.

Opción 1: hacer de toyNumber una variable miembro pública en una clase

class MyToy {
  public int toyNumber;
}

luego pase una referencia a un MyToy a su método.

void play(MyToy toy){  
    System.out.println("Toy number in play " + toy.toyNumber);   
    toy.toyNumber++;  
    System.out.println("Toy number in play after increement " + toy.toyNumber);   
}

Opción 2: devolver el valor en lugar de pasar por referencia

int play(int toyNumber){  
    System.out.println("Toy number in play " + toyNumber);   
    toyNumber++;  
    System.out.println("Toy number in play after increement " + toyNumber);   
    return toyNumber
}

Esta opción requeriría un pequeño cambio en el modo en callsite principal que se lee, toyNumber = temp.play(toyNumber);.

Opción 3: conviértalo en una clase o variable estática

Si las dos funciones son métodos en la misma clase o instancia de clase, puede convertir toyNumber en una variable miembro de clase.

Opción 4: cree una matriz de un solo elemento de tipo int y páselo

Esto se considera un truco, pero a veces se emplea para devolver valores de invocaciones de clases en línea.

void play(int [] toyNumber){  
    System.out.println("Toy number in play " + toyNumber[0]);   
    toyNumber[0]++;  
    System.out.println("Toy number in play after increement " + toyNumber[0]);   
}
latigazo
fuente
2
Explicación clara y especialmente buena para proporcionar múltiples opciones sobre cómo codificar el efecto deseado. ¡Directamente útil para algo en lo que estoy trabajando ahora mismo! Es una locura que esta pregunta se haya cerrado.
DarenW
1
Aunque su elección 1 hace lo que está tratando de transmitir, en realidad tuve que volver atrás y verificarlo. La pregunta pregunta si puede pasar por referencia; así que realmente deberías haber hecho los printlns en el mismo alcance en el que existe el juguete pasado a la función. De la forma en que lo tienes, los printlns se operan en el mismo alcance que el incremento que realmente no prueba el punto, por supuesto una copia local impresa después de un incremento tendrá un valor diferente, sin embargo, eso no significa que la referencia pasada lo tendrá. Una vez más, sin embargo, su ejemplo funciona, pero no prueba el punto.
zero298
No, creo que es correcto tal como está. Cada función "play ()" en mi respuesta anterior está pensada como un reemplazo directo de la función "play ()" original, que también imprime el valor antes y después del incremento. La pregunta original tiene println () en main que prueba pasar por referencia.
Laslowh
La opción 2 requiere una explicación más detallada: el sitio de llamada en main debe cambiarse a toyNumber = temp.play(toyNumber);para que funcione como se desea.
ToolmakerSteve
1
Esto es extremadamente feo y tiene una sensación de hackear ... ¿por qué algo tan básico no es parte del idioma?
shinzou
30

Java no se llama por referencia, solo se llama por valor

Pero todas las variables de tipo de objeto son en realidad punteros.

Entonces, si usa un objeto mutable, verá el comportamiento que desea

public class XYZ {

    public static void main(String[] arg) {
        StringBuilder toyNumber = new StringBuilder("5");
        play(toyNumber);
        System.out.println("Toy number in main " + toyNumber);
    }

    private static void play(StringBuilder toyNumber) {
        System.out.println("Toy number in play " + toyNumber);
        toyNumber.append(" + 1");
        System.out.println("Toy number in play after increement " + toyNumber);
    }
}

Salida de este código:

run:
Toy number in play 5
Toy number in play after increement 5 + 1
Toy number in main 5 + 1
BUILD SUCCESSFUL (total time: 0 seconds)

También puede ver este comportamiento en las bibliotecas estándar. Por ejemplo Collections.sort (); Colecciones.shuffle (); Estos métodos no devuelven una nueva lista, pero modifican su objeto de argumento.

    List<Integer> mutableList = new ArrayList<Integer>();

    mutableList.add(1);
    mutableList.add(2);
    mutableList.add(3);
    mutableList.add(4);
    mutableList.add(5);

    System.out.println(mutableList);

    Collections.shuffle(mutableList);

    System.out.println(mutableList);

    Collections.sort(mutableList);

    System.out.println(mutableList);

Salida de este código:

run:
[1, 2, 3, 4, 5]
[3, 4, 1, 5, 2]
[1, 2, 3, 4, 5]
BUILD SUCCESSFUL (total time: 0 seconds)
Kerem Baydoğan
fuente
2
Esta no es una respuesta a la pregunta. Habría respondido a la pregunta, si hubiera sugerido hacer una matriz de enteros que contuviera un solo elemento, y luego modificar ese elemento dentro del método play; ej mutableList[0] = mutableList[0] + 1;. Como sugiere Ernest Friedman-Hill.
ToolmakerSteve
Con no primitivos. El valor del objeto no es una referencia. Tal vez quisiera decir: "valor de parámetro" es "una referencia". Las referencias se pasan por valor.
vlakov
Estoy tratando de hacer algo similar, pero en su código, el método private static void play(StringBuilder toyNumber): ¿cómo lo llama si, por ejemplo, es public static int que devuelve un número entero? Porque tengo un método que devuelve un número, pero si no lo llamo en algún lugar, no está en uso.
frank17
18

Hacer una

class PassMeByRef { public int theValue; }

luego pase una referencia a una instancia de la misma. Tenga en cuenta que es mejor evitar un método que mute el estado a través de sus argumentos, especialmente en código paralelo.

Ingo
fuente
3
Votado en contra por mí. Esto está mal en muchos niveles. Java se pasa por valor, siempre. Sin excepciones.
duffymo
14
@duffymo: por supuesto, puede votar en contra de lo que quiera, pero ¿ha considerado lo que pidió el OP? Quiere pasar e int por referencia, y esto solo se logra si paso por valor una referencia a una instancia de lo anterior.
Ingo
7
@duffymo: ¿Eh? Está equivocado o malinterpreta la pregunta. Esta ES una de las formas tradicionales en Java de hacer lo que solicita el OP.
ToolmakerSteve
5
@duffymo: Tu comentario es incorrecto en muchos niveles. SO se trata de proporcionar respuestas y comentarios útiles, y simplemente rechaza una respuesta correcta solo porque (supongo) no se adapta a su estilo y filosofía de programación. ¿Podría al menos ofrecer una mejor explicación, o incluso una mejor respuesta a la pregunta original?
paercebal
2
@duffymo eso es exactamente lo que dije: pasar una referencia por valor. ¿Cómo crees que se logra pasar por referencia en idiomas que lo ralentizan?
Ingo
11

No puede pasar primitivas por referencia en Java. Todas las variables de tipo de objeto son en realidad punteros, por supuesto, pero las llamamos "referencias" y también siempre se pasan por valor.

En una situación en la que realmente necesita pasar una primitiva por referencia, lo que la gente hará a veces es declarar el parámetro como una matriz de tipo primitivo y luego pasar una matriz de un solo elemento como argumento. Entonces pasa una referencia int [1], y en el método, puede cambiar el contenido de la matriz.

Ernest Friedman-Hill
fuente
1
No, todas las clases contenedoras son inmutables , representan un valor fijo que no se puede cambiar una vez que se crea el objeto.
Ernest Friedman-Hill
1
"No se pueden pasar primitivas por referencia en Java": el OP parece entender esto, ya que contrastan C ++ (donde se puede) con Java.
Raedwald
Cabe señalar que "todas las clases contenedoras son inmutables", dicho por Ernest, se aplica al Integer, Double, Long, etc. incorporado de JDK. Puede omitir esta restricción con su clase contenedora personalizada, como hizo Ingo's Answer.
user3207158
10

Para una solución rápida, puede usar AtomicInteger o cualquiera de las variables atómicas que le permitirán cambiar el valor dentro del método usando los métodos incorporados. Aquí hay un código de muestra:

import java.util.concurrent.atomic.AtomicInteger;


public class PrimitivePassByReferenceSample {

    /**
     * @param args
     */
    public static void main(String[] args) {

        AtomicInteger myNumber = new AtomicInteger(0);
        System.out.println("MyNumber before method Call:" + myNumber.get());
        PrimitivePassByReferenceSample temp = new PrimitivePassByReferenceSample() ;
        temp.changeMyNumber(myNumber);
        System.out.println("MyNumber After method Call:" + myNumber.get());


    }

     void changeMyNumber(AtomicInteger myNumber) {
        myNumber.getAndSet(100);

    }

}

Salida:

MyNumber before method Call:0

MyNumber After method Call:100
premSiva
fuente
Utilizo y me gusta esta solución porque ofrece buenas acciones compuestas y es más fácil de integrar en programas concurrentes que ya o pueden querer usar estas clases atómicas en el futuro.
RAnders00
2
public static void main(String[] args) {
    int[] toyNumber = new int[] {5};
    NewClass temp = new NewClass();
    temp.play(toyNumber);
    System.out.println("Toy number in main " + toyNumber[0]);
}

void play(int[] toyNumber){
    System.out.println("Toy number in play " + toyNumber[0]);
    toyNumber[0]++;
    System.out.println("Toy number in play after increement " + toyNumber[0]);
}
itun
fuente