Comment faire l'équivalent du passage par référence pour les primitives en Java

116

Ce code 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);   
    }   
}  

affichera ceci:

 
Numéro de jouet en jeu 5  
Numéro de jouet en jeu après incrément 6  
Numéro de jouet dans le 5 principal  

En C ++, je peux passer la toyNumbervariable comme passe par référence pour éviter le shadowing, c'est-à-dire créer une copie de la même variable que ci-dessous:

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

et la sortie C ++ sera la suivante:

Numéro de jouet en jeu 5  
Numéro de jouet en jeu après incrément 6  
Numéro de jouet dans le 6 principal  

Ma question est la suivante: quel est le code équivalent en Java pour obtenir la même sortie que le code C ++, étant donné que Java est un passage par valeur plutôt que par référence ?

Étudiant
la source
22
Cela ne me semble pas être un doublon - la question supposée dupliquée traite du fonctionnement de java et de ce que signifient les termes, qui est éducation, tandis que cette question demande spécifiquement comment obtenir quelque chose qui ressemble à un comportement de passage par référence, quelque chose de beaucoup C Les programmeurs / C ++ / D / Ada pourraient se demander afin d'accomplir un travail pratique, sans se soucier de savoir pourquoi java est entièrement pass-by-value.
DarenW
1
@DarenW Je suis entièrement d'accord - j'ai voté pour la réouverture. Oh, et vous avez assez de réputation pour faire de même :-)
Duncan Jones
La discussion autour des primitives est plutôt trompeuse, car la question s'applique également aux valeurs de référence.
shmosel
"En C ++, je peux passer la variable toyNumber comme passe par référence pour éviter l'observation" - ce n'est pas de l'observation car la toyNumbervariable déclarée dans la mainméthode n'est pas dans la portée de la playméthode. L'observation en C ++ et Java ne se produit qu'en cas d'imbrication des étendues. Voir en.wikipedia.org/wiki/Variable_shadowing .
Stephen C

Réponses:

172

Vous avez plusieurs choix. Celui qui a le plus de sens dépend vraiment de ce que vous essayez de faire.

Choix 1: faire de toyNumber une variable membre publique dans une classe

class MyToy {
  public int toyNumber;
}

puis passez une référence à un MyToy à votre méthode.

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

Choix 2: renvoyer la valeur au lieu de passer par référence

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
}

Ce choix nécessiterait un petit changement au callsite dans le principal afin qu'il lit, toyNumber = temp.play(toyNumber);.

Choix 3: en faire une classe ou une variable statique

Si les deux fonctions sont des méthodes sur la même classe ou instance de classe, vous pouvez convertir toyNumber en variable membre de classe.

Choix 4: Créez un tableau d'élément unique de type int et transmettez-le

Ceci est considéré comme un hack, mais est parfois utilisé pour renvoyer des valeurs à partir d'appels de classes en ligne.

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]);   
}
Laslowh
la source
2
Explication claire et particulièrement utile pour fournir plusieurs choix sur la façon de coder pour l'effet souhaité. Directement utile pour quelque chose sur lequel je travaille en ce moment! Il est fou que cette question soit close.
DarenW
1
Bien que votre choix 1 fasse ce que vous essayez de transmettre, j'ai en fait dû revenir en arrière et le vérifier. La question demande si vous pouvez passer par référence; donc vraiment vous devriez avoir fait les printlns dans la même étendue que le jouet passé à la fonction existe. De la façon dont vous l'avez, les printlns sont opérés dans la même étendue que l'incrément qui ne prouve pas vraiment le point, bien sûr une copie locale imprimée après un incrément aura une valeur différente, mais cela ne signifie pas que la référence passée le sera. Encore une fois, votre exemple fonctionne, mais ne prouve pas le point.
zero298
Non, je pense que c'est correct tel quel. Chaque fonction «play ()» dans ma réponse ci-dessus est destinée à remplacer la fonction originale «play ()», qui imprimait également la valeur avant et après l'incrémentation. La question originale a le println () en principal qui prouve passer par référence.
laslowh
Le choix 2 nécessite des explications supplémentaires: le site d'appel doit en général être modifié toyNumber = temp.play(toyNumber);pour qu'il fonctionne comme vous le souhaitez.
ToolmakerSteve
1
C'est extrêmement moche et ça a une sensation hackish ... pourquoi quelque chose d'aussi basique ne fait pas partie du langage?
shinzou
30

Java n'est pas appelé par référence, il est appelé par valeur uniquement

Mais toutes les variables de type objet sont en fait des pointeurs.

Donc, si vous utilisez un objet mutable, vous verrez le comportement souhaité

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

Sortie de ce code:

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)

Vous pouvez également voir ce comportement dans les bibliothèques standard. Par exemple Collections.sort (); Collections.shuffle (); Ces méthodes ne retournent pas de nouvelle liste mais modifient leur objet argument.

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

Sortie de ce code:

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
la source
2
Ce n'est pas une réponse à la question. Il aurait répondu à la question, s'il avait suggéré de créer un tableau d'entiers contenant un seul élément, puis de modifier cet élément dans la méthode play; par exemple mutableList[0] = mutableList[0] + 1;. Comme le suggère Ernest Friedman-Hill.
ToolmakerSteve
Avec des non-primitifs. La valeur de l'objet n'est pas une référence. Peut-être que vous vouliez dire: «valeur de paramètre» est «une référence». Les références sont passées par valeur.
vlakov
J'essaie de faire quelque chose de similaire, mais dans votre code, la méthode private static void play(StringBuilder toyNumber)- comment l'appeler si c'est par exemple public static int qui renvoie un entier? Parce que j'ai une méthode qui renvoie un nombre mais si je ne l'appelle pas quelque part, elle n'est pas utilisée.
frank17
18

Faire un

class PassMeByRef { public int theValue; }

puis passez une référence à une instance de celui-ci. Notez qu'une méthode qui fait muter l'état via ses arguments est mieux évitée, en particulier dans le code parallèle.

Ingo
la source
3
Je suis contre moi. C'est faux sur tellement de niveaux. Java passe par valeur - toujours. Aucune exception.
duffymo
14
@duffymo - Bien sûr, vous pouvez voter à votre guise - mais avez-vous considéré ce que le PO a demandé? Il veut passer et int par référence - et cela est accompli si je passe par valeur une référence à une instance de ce qui précède.
Ingo
7
@duffymo: Hein? Vous vous trompez ou vous comprenez mal la question. C'EST une des façons traditionnelles en Java de faire ce que les demandes de OP.
ToolmakerSteve
5
@duffymo: Votre commentaire est faux à bien des égards. Ainsi, il s'agit de fournir des réponses et des commentaires utiles, et il vous suffit de rejeter une réponse correcte simplement parce que (je suppose) qu'elle ne correspond pas à votre style de programmation et à votre philosophie. Pourriez-vous au moins offrir une meilleure explication, ou même une meilleure réponse à la question initiale?
paercebal
2
@duffymo c'est exactement ce que j'ai dit: passer une référence par valeur. Comment pensez-vous que le pass by ref est accompli dans des langages qui le ralentissent?
Ingo
11

Vous ne pouvez pas transmettre de primitives par référence en Java. Toutes les variables de type objet sont en fait des pointeurs, bien sûr, mais nous les appelons des "références", et elles sont également toujours passées par valeur.

Dans une situation où vous avez vraiment besoin de passer une primitive par référence, ce que les gens feront parfois est de déclarer le paramètre comme un tableau de type primitif, puis de passer un tableau à un seul élément comme argument. Vous passez donc une référence int [1], et dans la méthode, vous pouvez modifier le contenu du tableau.

Ernest Friedman-Hill
la source
1
Non - toutes les classes wrapper sont immuables - elles représentent une valeur fixe qui ne peut pas être modifiée une fois l'objet créé.
Ernest Friedman-Hill
1
"Vous ne pouvez pas passer de primitives par référence en Java": l'OP semble comprendre cela, car ils opposent C ++ (où vous pouvez) avec Java.
Raedwald
Il convient de noter que «toutes les classes wrapper sont immuables», dit par Ernest, s'applique aux Integer, Double, Long, etc. intégrés de JDK. Vous pouvez contourner cette restriction avec votre classe wrapper personnalisée, comme l'a fait Ingo's Answer.
user3207158
10

Pour une solution rapide, vous pouvez utiliser AtomicInteger ou l'une des variables atomiques qui vous permettront de modifier la valeur à l'intérieur de la méthode à l'aide des méthodes intégrées. Voici un exemple de code:

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

    }

}

Production:

MyNumber before method Call:0

MyNumber After method Call:100
premSiva
la source
J'utilise et j'aime cette solution car elle offre de belles actions composées et est plus facile à intégrer dans des programmes concurrents qui utilisent déjà ou peuvent vouloir utiliser ces classes atomiques à l'avenir.
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
la source