Come eseguire l'equivalente del passaggio per riferimento per le primitive in Java


116

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

produrrà questo:

 
Numero del giocattolo in gioco 5  
Numero del giocattolo in gioco dopo l'accordo 6  
Numero del giocattolo nella parte principale 5  

In C ++ posso passare la toyNumbervariabile come passaggio per riferimento per evitare lo shadowing, ovvero creare una copia della stessa variabile come di seguito:

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

e l'output C ++ sarà questo:

Numero del giocattolo in gioco 5  
Numero del giocattolo in gioco dopo l'accordo 6  
Numero del giocattolo nella parte principale 6  

La mia domanda è: qual è il codice equivalente in Java per ottenere lo stesso output del codice C ++, dato che Java viene passato per valore anziché per riferimento ?


22
Questo non mi sembra un duplicato: la domanda presumibilmente duplicata riguarda come funziona java e cosa significano i termini, che è istruzione, mentre questa domanda chiede specificamente come ottenere qualcosa di simile al comportamento pass-by-reference, qualcosa che molti C I programmatori di / C ++ / D / Ada potrebbero chiedersi per portare a termine il lavoro pratico, senza preoccuparsi del motivo per cui java è tutto valore di passaggio.
DarenW

1
@ DarenW Sono pienamente d'accordo - ho votato per la riapertura. Oh, e hai abbastanza reputazione per fare lo stesso :-)
Duncan Jones

La discussione sulle primitive è piuttosto fuorviante, poiché la domanda si applica ugualmente ai valori di riferimento.
shmosel

"In C ++ posso passare la variabile toyNumber come passaggio per riferimento per evitare lo shadowing" - questo non è lo shadowing perché la toyNumbervariabile dichiarata nel mainmetodo non è nell'ambito del playmetodo. Lo shadowing in C ++ e Java si verifica solo quando è presente l'annidamento degli ambiti. Vedi en.wikipedia.org/wiki/Variable_shadowing .
Stephen C

Risposte:


172

Hai diverse scelte. Quello che ha più senso dipende davvero da cosa stai cercando di fare.

Scelta 1: rendere toyNumber una variabile membro pubblica in una classe

class MyToy {
  public int toyNumber;
}

quindi passa un riferimento a un MyToy al tuo metodo.

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

Scelta 2: restituire il valore invece di passare per riferimento

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
}

Questa scelta avrebbe richiesto una piccola modifica al callsite in modo principale che si legge, toyNumber = temp.play(toyNumber);.

Scelta 3: rendila una variabile di classe o statica

Se le due funzioni sono metodi sulla stessa classe o istanza di classe, è possibile convertire toyNumber in una variabile membro della classe.

Scelta 4: creare un singolo array di elementi di tipo int e passarlo

Questo è considerato un hack, ma a volte viene utilizzato per restituire valori da invocazioni di classi inline.

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

2
Spiegazione chiara e particolarmente utile per fornire scelte multiple su come codificare per l'effetto desiderato. Direttamente utile per qualcosa su cui sto lavorando in questo momento! È assurdo che questa domanda sia stata chiusa.
DarenW

1
Anche se la tua scelta 1 fa quello che stai cercando di trasmettere, in realtà ho dovuto tornare indietro e ricontrollarla. La domanda chiede se puoi passare per riferimento; quindi in realtà avresti dovuto eseguire le stampe nello stesso ambito in cui esiste il giocattolo passato alla funzione. Dal modo in cui lo hai, le stampe sono gestite nello stesso ambito dell'incremento che in realtà non prova il punto, di CORSO una copia locale stampata dopo un incremento avrà un valore diverso, tuttavia ciò non significa che il riferimento passato lo farà. Anche in questo caso, il tuo esempio funziona, ma non prova il punto.
zero298

No, penso che sia corretto così com'è. Ogni funzione "play ()" nella mia risposta sopra è intesa come una sostituzione immediata per la funzione "play ()" originale, che ha anche stampato il valore prima e dopo l'incremento. La domanda originale ha println () in main che risulta passata per riferimento.
ultimo

La scelta 2 richiede un'ulteriore spiegazione: il sito della chiamata in main deve essere cambiato in toyNumber = temp.play(toyNumber);affinché funzioni come desiderato.
ToolmakerSteve

1
Questo è estremamente brutto e ha un aspetto hacker ... perché qualcosa di così semplice non fa parte del linguaggio?
shinzou

30

Java non è chiamata per riferimento , è chiamata solo per valore

Ma tutte le variabili di tipo oggetto sono in realtà puntatori.

Quindi, se usi un oggetto mutevole, vedrai il comportamento che desideri

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

Output di questo codice:

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)

Puoi vedere questo comportamento anche nelle librerie standard. Ad esempio Collections.sort (); Collections.shuffle (); Questi metodi non restituiscono un nuovo elenco ma modificano il suo oggetto argomento.

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

Output di questo codice:

run:
[1, 2, 3, 4, 5]
[3, 4, 1, 5, 2]
[1, 2, 3, 4, 5]
BUILD SUCCESSFUL (total time: 0 seconds)

2
Questa non è una risposta alla domanda. Avrebbe risposto alla domanda se avesse suggerito di creare un array intero che conteneva un singolo elemento e quindi di modificare quell'elemento all'interno del metodo play; ad es mutableList[0] = mutableList[0] + 1;. Come suggerisce Ernest Friedman-Hill.
ToolmakerSteve

Con non primitive. Il valore dell'oggetto non è un riferimento. Forse volevi dire: "valore del parametro" è "un riferimento". I riferimenti vengono passati per valore.
vlakov

Sto cercando di fare qualcosa di simile ma nel tuo codice, il metodo private static void play(StringBuilder toyNumber)- come lo chiami se ad esempio è public static int che restituisce un numero intero? Perché ho un metodo che restituisce un numero ma se non lo chiamo da qualche parte, non è in uso.
frank17

18

Fare un

class PassMeByRef { public int theValue; }

quindi passare un riferimento a un'istanza di esso. Si noti che è meglio evitare un metodo che muta lo stato attraverso i suoi argomenti, specialmente nel codice parallelo.


3
Downvoted da me. Questo è sbagliato sotto molti aspetti. Java è passato per valore - sempre. Nessuna eccezione.
duffymo

14
@duffymo - Ovviamente puoi downvote come preferisci - ma hai considerato ciò che l'OP ha chiesto? Vuole passare e int per riferimento - e questo si ottiene se passo per valore un riferimento a un'istanza di quanto sopra.
Ingo

7
@ Duffymo: eh? Ti sbagli o fraintendi la domanda. Questo è uno dei modi tradizionali in Java di fare ciò che richiede l'OP.
ToolmakerSteve

5
@duffymo: il tuo commento è sbagliato su così tanti livelli. COSÌ si tratta di fornire risposte e commenti utili, e semplicemente sottovaluti una risposta corretta solo perché (immagino) non si adatta al tuo stile di programmazione e filosofia. Potresti almeno offrire una spiegazione migliore, o anche una risposta migliore alla domanda originale?
paercebal

2
@duffymo è esattamente quello che ho detto: passa un riferimento per valore. Come pensi che il passaggio per ref venga eseguito in lingue che lo rallentano?
Ingo

11

Non è possibile passare primitive per riferimento in Java. Tutte le variabili di tipo oggetto sono in realtà puntatori, ovviamente, ma le chiamiamo "riferimenti", e sono anche sempre passate per valore.

In una situazione in cui è davvero necessario passare una primitiva per riferimento, ciò che le persone a volte fanno è dichiarare il parametro come un array di tipo primitivo, quindi passare un array a un elemento come argomento. Quindi si passa un riferimento int [1] e nel metodo è possibile modificare il contenuto dell'array.


1
No - tutte le classi wrapper sono immutabili - rappresentano un valore fisso che non può essere modificato una volta creato l'oggetto.
Ernest Friedman-Hill

1
"Non si possono passare primitive per riferimento in Java": l'OP sembra capirlo, in quanto contrasta il C ++ (dove è possibile) con Java.
Raedwald

Va notato che "tutte le classi wrapper sono immutabili" dette da Ernest si applicano a Integer, Double, Long, ecc. Incorporati di JDK. Puoi aggirare questa restrizione con la tua classe wrapper personalizzata, come ha fatto Ingo's Answer.
user3207158

10

Per una soluzione rapida, puoi utilizzare AtomicInteger o una qualsiasi delle variabili atomiche che ti consentiranno di modificare il valore all'interno del metodo utilizzando i metodi incorporati. Ecco il codice di esempio:

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

    }

}

Produzione:

MyNumber before method Call:0

MyNumber After method Call:100

Uso e mi piace questa soluzione perché offre belle azioni composte ed è più facile da integrare in programmi concorrenti che già o potrebbero voler utilizzare queste classi atomiche in 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]);
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.