Domanda Java è "pass-by-reference" o "pass-by-value"?


Ho sempre pensato che Java fosse passaggio per riferimento .

Tuttavia, ho visto un paio di post del blog (ad esempio, questo blog ) che afferma che non lo è.

Non credo di capire la distinzione che stanno facendo.

Qual è la spiegazione?


5481


origine


risposte:


Java è sempre pass-by-value . Sfortunatamente, hanno deciso di chiamare la posizione di un oggetto come "riferimento". Quando passiamo il valore di un oggetto, stiamo passando il riferimento  ad esso. Questo è fonte di confusione per i principianti.

Va così:

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
}

Nell'esempio sopra aDog.getName() tornerà ancora "Max". Il valore aDog entro main non è cambiato nella funzione foo con il Dog  "Fifi" come il riferimento all'oggetto viene passato per valore. Se fosse passato per riferimento, allora il aDog.getName() in main sarebbe tornato "Fifi" dopo la chiamata a foo.

Allo stesso modo:

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

Nell'esempio sopra, Fifi è il nome del cane dopo la chiamata a foo(aDog) perché il nome dell'oggetto è stato impostato all'interno di foo(...). Qualsiasi operazione foo si esibisce d sono tali che, per tutti gli scopi pratici, vengono eseguiti su aDog stesso (tranne quando d è cambiato per indicare un diverso Dog esempio come d = new Dog("Boxer")).


4731



Ho appena notato che hai fatto riferimento a te il mio articolo .

La specifica Java dice che tutto in Java è pass-by-value. Non esiste una cosa come "pass-by-reference" in Java.

La chiave per capire questo è qualcosa di simile

Dog myDog;

è non  un cane; in realtà è a pointer  a un cane.

Cosa significa, è quando hai

Dog myDog = new Dog("Rover");
foo(myDog);

stai essenzialmente passando il indirizzo  del creato Dog oggetto al foo metodo.

(Dico essenzialmente perché i puntatori Java non sono indirizzi diretti, ma è più facile pensarli in quel modo)

Supponiamo che Dog l'oggetto risiede all'indirizzo di memoria 42. Ciò significa che passiamo 42 al metodo.

se il metodo è stato definito come

public void foo(Dog someDog) {
    someDog.setName("Max");     // AAA
    someDog = new Dog("Fifi");  // BBB
    someDog.setName("Rowlf");   // CCC
}

diamo un'occhiata a cosa sta succedendo.

  • il parametro someDog è impostato sul valore 42
  • alla riga "AAA"
    • someDog è seguito al Dog punta a (il Dog oggetto all'indirizzo 42)
    • quella Dog (quello all'indirizzo 42) è invitato a cambiare il suo nome in Max
  • alla riga "BBB"
    • un nuovo Dog è creato. Diciamo che è all'indirizzo 74
    • noi assegniamo il parametro someDog a 74
  • alla linea "CCC"
    • someDog è seguito al Dog punta a (il Dog oggetto all'indirizzo 74)
    • quella Dog (quello all'indirizzo 74) è invitato a cambiare il suo nome in Rowlf
  • poi, torniamo

Ora pensiamo a cosa succede al di fuori del metodo:

Did myDog modificare?

C'è la chiave.

Tenendo presente questo myDog è un pointer e non un reale Dog, la risposta è no. myDog ha ancora il valore 42; sta ancora puntando all'originale Dog(ma nota che a causa della linea "AAA", il suo nome ora è "Max" - ancora lo stesso Cane; myDogil valore non è cambiato.)

È perfettamente valido per Seguire  un indirizzo e cambiare ciò che è alla fine di esso; questo non cambia la variabile, comunque.

Java funziona esattamente come C. È possibile assegnare un puntatore, passare il puntatore a un metodo, seguire il puntatore nel metodo e modificare i dati puntati. Tuttavia, non puoi cambiare dove punta quel puntatore.

In C ++, Ada, Pascal e in altre lingue che supportano il pass-by-reference, puoi effettivamente cambiare la variabile che è stata passata.

Se Java avesse una semantica pass-by-reference, il foo il metodo che abbiamo definito sopra sarebbe cambiato dove myDog stava indicando quando è stato assegnato someDog on line BBB.

Pensa ai parametri di riferimento come alias per la variabile passata. Quando viene assegnato quell'alias, lo è anche la variabile passata.


2637



Java passa sempre argomenti per valore NON per riferimento.


Lasciatemi spiegare questo attraverso un esempio :

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

Lo spiegherò nei passaggi:

  1. Dichiarazione di un riferimento denominato f di tipo Foo e assegnarlo a un nuovo oggetto di tipo Foo con un attributo "f".

    Foo f = new Foo("f");
    

    enter image description here

  2. Dal lato del metodo, un riferimento di tipo Foo con un nome a è dichiarato ed è inizialmente assegnato a null.

    public static void changeReference(Foo a)
    

    enter image description here

  3. Come tu chiami il metodo changeReference, il riferimento a sarà assegnato all'oggetto che viene passato come argomento.

    changeReference(f);
    

    enter image description here

  4. Dichiarazione di un riferimento denominato b di tipo Foo e assegnarlo a un nuovo oggetto di tipo Foo con un attributo "b".

    Foo b = new Foo("b");
    

    enter image description here

  5. a = b sta riassegnando il riferimento a NON f all'oggetto il cui attributo è "b".

    enter image description here


  6. Come chiami modifyReference(Foo c) metodo, un riferimento c viene creato e assegnato all'oggetto con attributo "f".

    enter image description here

  7. c.setAttribute("c"); cambierà l'attributo dell'oggetto che fa riferimento c punta ad esso, ed è lo stesso oggetto che riferimento f indica ad esso.

    enter image description here

Spero che tu capisca ora come funzionano gli oggetti come oggetti in Java :)


1388



Questo ti darà alcuni spunti su come Java funzioni davvero al punto che nella tua prossima discussione su Java che passa per riferimento o che passi per valore sorriderà :-)

Passo uno per favore cancella dalla tua mente quella parola che inizia con 'p' "_ _ _ _ _ _" ", specialmente se provieni da altri linguaggi di programmazione. Java e 'p' non possono essere scritti nello stesso libro, forum o anche txt.

La seconda fase ricorda che quando si passa un oggetto in un metodo si passa il riferimento all'oggetto e non l'oggetto stesso.

  • Alunno : Master, significa che Java è pass-by-reference?
  • Maestro : Cavalletta, n.

Ora pensa a che cosa fa / è un riferimento / variabile di un oggetto:

  1. Una variabile contiene i bit che indicano alla JVM come raggiungere l'oggetto di riferimento in memoria (Heap).
  2. Quando si passano gli argomenti a un metodo NON si sta passando la variabile di riferimento, ma una copia dei bit nella variabile di riferimento . Qualcosa del genere: 3bad086a. 3bad086a rappresenta un modo per arrivare all'oggetto passato.
  3. Quindi stai solo passando a 3bad086a che è il valore del riferimento.
  4. Stai passando il valore del riferimento e non il riferimento stesso (e non l'oggetto).
  5. Questo valore è in realtà COPIATO e dato al metodo .

Di seguito (per favore non provare a compilare / eseguire questo ...):

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

Che succede?

  • La variabile persona  viene creato nella riga n. 1 ed è nullo all'inizio.
  • Un nuovo Oggetto Persona viene creato nella riga n. 2, memorizzato nella memoria, e la variabile persona  viene dato il riferimento all'oggetto Person. Cioè, il suo indirizzo. Diciamo 3bad086a.
  • La variabile persona  tenendo l'indirizzo dell'oggetto viene passato alla funzione nella riga # 3.
  • Alla riga 4 puoi ascoltare il suono del silenzio
  • Controlla il commento sulla linea # 5
  • Una variabile locale del metodo - anotherReferenceToTheSamePersonObject - viene creato e poi arriva la magia nella riga n. 6:
    • La variabile / riferimento persona  viene copiata bit per bit e passata a anotherReferenceToTheSamePersonObject  all'interno della funzione.
    • Non vengono create nuove istanze di Person.
    • Entrambi " persona " e " anotherReferenceToTheSamePersonObject "mantenere lo stesso valore di 3bad086a.
    • Non provare questo, ma person == anotherReferenceToTheSamePersonObject sarebbe vero.
    • Entrambe le variabili hanno COPIE IDENTICHE del riferimento e si riferiscono entrambi allo stesso oggetto Person, allo SAME Object sull'Heap e NON A COPY.

Un'immagine vale più di mille parole:

Pass by Value

Nota che le frecce anotherReferenceToTheSamePersonObject sono dirette verso l'Oggetto e non verso la persona variabile!

Se non l'hai capito, fidati e ricorda che è meglio dirlo Java è passato per valore . Bene, passare per valore di riferimento . Oh bene, ancora meglio è pass-by-copy-of-the-variabile valore! ;)

Adesso sentiti libero di odiarmi, ma nota questo dato non vi è alcuna differenza tra il passaggio di tipi di dati primitivi e oggetti  quando si parla di argomenti del metodo.

Si passa sempre una copia dei bit del valore del riferimento!

  • Se si tratta di un tipo di dati primitivo, questi bit conterranno il valore del tipo di dati primitivi stesso.
  • Se è un oggetto, i bit conterranno il valore dell'indirizzo che dice alla JVM come raggiungere l'oggetto.

Java è pass-by-value perché all'interno di un metodo puoi modificare l'oggetto di riferimento quanto vuoi ma non importa quanto duramente ci provi non sarai mai in grado di modificare la variabile passata che manterrà il riferimento (non p _ _ _ _ _ _ _) lo stesso oggetto, non importa cosa!


La funzione changeName precedente non sarà mai in grado di modificare il contenuto effettivo (i valori di bit) del riferimento passato. In altre parole changeName non può rendere Person persona riferirsi a un altro oggetto.


Certo che puoi tagliarlo brevemente e dillo Java è un valore pass-by!


651



Java passa sempre per valore, senza eccezioni, mai .

Quindi, come mai qualcuno può essere affatto confuso da questo, e credere che Java sia passato per riferimento, o pensa di avere un esempio di Java che agisce come passaggio per riferimento? Il punto chiave è che Java mai  fornisce accesso diretto ai valori di oggetti stessi , in qualunque  circostanze. L'unico accesso agli oggetti è attraverso a riferimento  a quell'oggetto Perché gli oggetti Java sono sempre  accessibile tramite un riferimento, piuttosto che direttamente, è comune parlare di campi e variabili e argomenti del metodo  come oggetti , quando pedanticamente sono solo riferimenti agli oggetti . La confusione deriva da questo (rigorosamente, errato) cambiamento nella nomenclatura.

Quindi, quando si chiama un metodo

  • Per argomenti primitivi ( int, long, ecc.), il passaggio per valore è il valore reale  del primitivo (per esempio, 3).
  • Per gli oggetti, il valore di passaggio è il valore di il riferimento all'oggetto .

Quindi se lo hai doSomething(foo) e public void doSomething(Foo foo) { .. } i due Foos hanno copiato Riferimenti  che puntano agli stessi oggetti.

Naturalmente, passando per valore, un riferimento ad un oggetto assomiglia molto (ed è indistinguibile in pratica da) che passa un oggetto per riferimento.


561



Java passa i riferimenti in base al valore.

Quindi non puoi cambiare il riferimento che viene passato.


278



Mi sento di discutere su "pass-by-reference vs pass-by-value" non è super-utile.

Se dici "Java è pass-by-any (riferimento / valore)", in entrambi i casi non fornisci una risposta completa. Ecco alcune informazioni aggiuntive che si spera possano aiutare a capire cosa sta succedendo nella memoria.

Crash course su stack / heap prima di arrivare all'implementazione Java: I valori vanno avanti e indietro nello stack in un modo ordinato, come una pila di piatti in una caffetteria. La memoria nell'heap (noto anche come memoria dinamica) è casuale e disorganizzata. La JVM trova lo spazio dove può e lo libera in quanto le variabili che lo utilizzano non sono più necessarie.

Va bene. Prima di tutto, i primitivi locali vanno in pila. Quindi questo codice:

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

risultati in questo:

primitives on the stack

Quando dichiari e instanzia un oggetto. L'oggetto reale va in pila. Cosa va in pila? L'indirizzo dell'oggetto sull'heap. I programmatori C ++ chiamerebbero questo un puntatore, ma alcuni sviluppatori Java sono contro la parola "puntatore". Qualunque cosa. Sappi solo che l'indirizzo dell'oggetto va in pila.

Così:

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

a b*7ch aint one!

Un array è un oggetto, quindi va anche nell'heap. E gli oggetti nell'array? Prendono il loro spazio nell'heap e l'indirizzo di ciascun oggetto va all'interno dell'array.

JButton[] marxBros = new JButton[3];
marxBros[0] = new JButton("Groucho");
marxBros[1] = new JButton("Zeppo");
marxBros[2] = new JButton("Harpo");

marx brothers

Quindi, cosa viene passato quando chiami un metodo? Se passi in un oggetto, ciò che stai effettivamente passando è l'indirizzo dell'oggetto. Qualcuno potrebbe dire il "valore" dell'indirizzo, e alcuni dicono che è solo un riferimento all'oggetto. Questa è la genesi della guerra santa tra i fautori del "riferimento" e del "valore". Ciò che chiami non è tanto importante quanto quello che capisci che ciò che viene passato è l'indirizzo dell'oggetto.

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

Viene creata una stringa e lo spazio per esso viene allocato nell'heap e l'indirizzo della stringa viene archiviato nello stack e viene fornito l'identificatore hisName, poiché l'indirizzo della seconda stringa è uguale al primo, non viene creata una nuova stringa e non viene allocato alcun nuovo spazio heap, ma viene creato un nuovo identificatore nello stack. Quindi chiamiamo shout(): viene creato un nuovo stack frame e un nuovo identificatore, name viene creato e assegnato l'indirizzo della stringa già esistente.

la da di da da da da

Quindi, valore, riferimento? Tu dici "patata".


198



Solo per mostrare il contrasto, confronta quanto segue C ++  e Giava  frammenti:

In C ++: Nota: codice errato - perdite di memoria!   Ma dimostra il 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"
}

In 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 ha solo i due tipi di passaggio: per valore per i tipi predefiniti e per valore del puntatore per i tipi di oggetto.


161



Java passa i riferimenti agli oggetti in base al valore.


145