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


6580

Ho sempre pensato che Java fosse pass-per-riferimento .

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

Non credo di capire la distinzione che stanno facendo.

Qual è la spiegazione?


706
Credo che gran parte della confusione su questo tema abbia a che fare con il fatto che persone diverse hanno definizioni diverse del termine "riferimento". Le persone che provengono da uno sfondo C ++ presumono che "riferimento" debba significare ciò che significava in C ++, le persone che provengono da uno sfondo C assumono che "riferimento" debba essere lo stesso di "puntatore" nella loro lingua e così via. Se è corretto dire che Java passa per riferimento dipende davvero da cosa si intende per "riferimento".
Gravità,

119
Cerco di utilizzare costantemente la terminologia presente nell'articolo sulla strategia di valutazione . Va notato che, anche se l'articolo sottolinea che i termini variano notevolmente a seconda della comunità, sottolinea che la semantica call-by-valuee call-by-referencedifferisce in modo molto cruciale . (Personalmente preferisco usare call-by-object-sharingquesti giorni call-by-value[-of-the-reference], poiché questo descrive la semantica ad alto livello e non crea un conflitto call-by-value, che è l'implementazione sottostante.)

59
@Gravity: puoi andare e inserire il tuo commento su un cartellone ENORME o qualcosa del genere? Questo è l'intero problema in breve. E dimostra che tutta questa faccenda è semantica. Se non siamo d'accordo sulla definizione di base di un riferimento, allora non saremo d'accordo sulla risposta a questa domanda :)
MadConan,

30
Penso che la confusione sia "passa per riferimento" contro "semantica di riferimento". Java è pass-by-value con semantica di riferimento.
spruzzo

11
@Gravity, sebbene tu abbia assolutamente ragione nel dire che le persone provenienti dal C ++ avranno istintivamente una diversa intuizione riguardo al termine "riferimento", personalmente credo che il corpo sia più sepolto nel "da". "Passare" è confuso in quanto è assolutamente distinto da "Passare un" in Java. In C ++, tuttavia, colloquialmente non lo è. In C ++ puoi dire "passare un riferimento", e si capisce che passerà il swap(x,y)test .

Risposte:


5844

Java è sempre pass-by-value . Sfortunatamente, quando passiamo il valore di un oggetto, passiamo il riferimento ad esso. Questo è fonte di confusione per i principianti.

Va così:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    Dog oldDog = aDog;

    // 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
    aDog == oldDog; // true
}

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 aDogall'interno mainnon viene modificato nella funzione foocon il Dog "Fifi"riferimento dell'oggetto passato per valore. Se fosse passato per riferimento, l' aDog.getName()ini mainsarebbe tornato "Fifi"dopo la chiamata a foo.

Allo stesso modo:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    Dog oldDog = aDog;

    foo(aDog);
    // when foo(...) returns, the name of the dog has been changed to "Fifi"
    aDog.getName().equals("Fifi"); // true
    // but it is still the same dog:
    aDog == oldDog; // 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 foo(aDog)perché il nome dell'oggetto è stato impostato all'interno di foo(...). Operazioni che fooesegue su dtali che, per tutti gli scopi pratici, vengono eseguite su aDog, ma è non possibile modificare il valore della variabile aDogstessa.


263
Non confonde leggermente il problema con i dettagli interni? Non c'è alcuna differenza concettuale tra "passare un riferimento" e "passare il valore di un riferimento", supponendo che tu intenda "il valore del puntatore interno all'oggetto".
izb,

383
Ma c'è una sottile differenza. Guarda il primo esempio. Se fosse puramente passato per riferimento, aDog.name sarebbe "Fifi". Non lo è - il riferimento che stai ricevendo è un riferimento di valore che se sovrascritto verrà ripristinato all'uscita dalla funzione.
Erlando,

300
@Lorenzo: No, in Java tutto viene passato per valore. Le primitive vengono passate per valore e i riferimenti agli oggetti vengono passati per valore. Gli oggetti stessi non vengono mai passati a un metodo, ma gli oggetti sono sempre nell'heap e solo un riferimento all'oggetto viene passato al metodo.
Esko Luontola,

295
Il mio tentativo di un buon modo per visualizzare il passaggio di oggetti: immagina un pallone. Chiamare un fxn è come legare una seconda stringa al palloncino e passare la linea al fxn. parametro = new Balloon () taglierà quella stringa e creerà un nuovo fumetto (ma questo non ha alcun effetto sul fumetto originale). parametro.pop () lo farà comunque apparire perché segue la stringa con lo stesso fumetto originale. Java è pass per valore, ma il valore passato non è profondo, è al livello più alto, ovvero una primitiva o un puntatore. Non confonderlo con un profondo valore di passaggio in cui l'oggetto viene interamente clonato e passato.
Dhackner,

150
Ciò che confonde è che i riferimenti agli oggetti sono in realtà puntatori. All'inizio SUN li chiamava puntatori. Quindi il marketing ha informato che "puntatore" era una parolaccia. Ma vedi ancora la nomenclatura "corretta" in NullPointerException.
contratto del Prof. Falken ha violato il

3035

Ho appena notato che hai fatto riferimento al mio articolo .

Le specifiche Java indicano che tutto in Java è pass-by-value. Non esiste "pass-by-reference" in Java.

La chiave per capirlo è che qualcosa del genere

Dog myDog;

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

Ciò significa che è quando hai

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

stai essenzialmente passando l' indirizzoDog dell'oggetto creato al foometodo.

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

Supponiamo che l' Dogoggetto risieda all'indirizzo di memoria 42. Ciò significa che passiamo 42 al metodo.

se il metodo fosse 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 linea "AAA"
    • someDogè seguito da Dogesso indica (l' Dogoggetto all'indirizzo 42)
    • quello Dog(quello all'indirizzo 42) è invitato a cambiare il suo nome in Max
  • alla riga "BBB"
    • Dogviene creato un nuovo . Diciamo che è all'indirizzo 74
    • assegniamo il parametro someDoga 74
  • alla riga "CCC"
    • someDog è seguito da Dogesso punta a (l' Dogoggetto all'indirizzo 74)
    • quello Dog(quello all'indirizzo 74) è invitato a cambiare il suo nome in Rowlf
  • quindi, torniamo

Ora pensiamo a cosa succede al di fuori del metodo:

È myDogcambiato?

C'è la chiave.

Tenendo presente che myDogè un puntatore e non un reale Dog, la risposta è NO. myDogha ancora il valore 42; punta ancora all'originale Dog(ma nota che a causa della linea "AAA", il suo nome è ora "Max" - sempre lo stesso Cane; myDogil valore non è cambiato.)

È perfettamente valido seguire un indirizzo e cambiare ciò che è alla fine; ciò 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 a cui è stato puntato. Tuttavia, non è possibile modificare la posizione del puntatore.

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

Se Java avesse una semantica pass-by-reference, il foometodo che abbiamo definito sopra sarebbe cambiato dove myDogpuntava quando era assegnato someDogsulla linea BBB.

Pensa ai parametri di riferimento come alias per la variabile passata. Quando viene assegnato tale alias, lo è anche la variabile che è stata passata.


164
Questo è il motivo per cui il ritornello comune "Java non ha puntatori" è così fuorviante.
Beska,

134
Ti sbagli, imho. "Tenendo presente che myDog è un puntatore e non un vero cane, la risposta è NO. MyDog ha ancora il valore 42; punta ancora al cane originale." myDog ha valore 42 ma ora l'argomento name contiene "Max", anziché "Rover" sulla riga // AAA.
Özgür,

180
Pensaci in questo modo. Qualcuno ha l'indirizzo di Ann Arbor, MI (la mia città, GO BLUE!) Su un foglietto chiamato "annArborLocation". Lo copi su un pezzo di carta chiamato "myDestination". Puoi guidare fino a "myDestination" e piantare un albero. Potresti aver cambiato qualcosa sulla città in quella posizione, ma non cambia il LAT / LON che è stato scritto su entrambi i fogli. È possibile modificare LAT / LON su "myDestination" ma non cambia "annArborLocation". Questo aiuta?
Scott Stanchfield,

43
@Scott Stanchfield: ho letto il tuo articolo circa un anno fa e mi ha davvero aiutato a chiarire le cose. Grazie! Vorrei suggerire umilmente una piccola aggiunta: dovresti menzionare che in realtà esiste un termine specifico che descrive questa forma di "chiamata per valore in cui il valore è un riferimento" che è stata inventata da Barbara Liskov per descrivere la strategia di valutazione del suo linguaggio CLU in 1974, al fine di evitare confusione come quella che il tuo articolo affronta: chiamata per condivisione (a volte chiamata chiamata per condivisione di oggetti o semplicemente chiamata per oggetto ), che descrive praticamente perfettamente la semantica.
Jörg W Mittag,

29
@Gevorg - I linguaggi C / C ++ non possiedono il concetto di "puntatore". Esistono altri linguaggi che utilizzano i puntatori ma non consentono gli stessi tipi di manipolazione dei puntatori consentiti da C / C ++. Java ha puntatori; sono solo protetti contro il male.
Scott Stanchfield,

1741

Java passa sempre argomenti per valore , NON per riferimento.


Lasciami spiegare questo con 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");
     }

}

Spiegherò questo a passi:

  1. Dichiarare un riferimento denominato fdi tipo Fooe assegnargli un nuovo oggetto di tipo Foocon un attributo "f".

    Foo f = new Foo("f");

    inserisci qui la descrizione dell'immagine

  2. Dal lato del metodo, viene dichiarato un riferimento di tipo Foocon un nome ae inizialmente assegnato null.

    public static void changeReference(Foo a)

    inserisci qui la descrizione dell'immagine

  3. Quando si chiama il metodo changeReference, al riferimento averrà assegnato l'oggetto che viene passato come argomento.

    changeReference(f);

    inserisci qui la descrizione dell'immagine

  4. Dichiarare un riferimento denominato bdi tipo Fooe assegnargli un nuovo oggetto di tipo Foocon un attributo "b".

    Foo b = new Foo("b");

    inserisci qui la descrizione dell'immagine

  5. a = beffettua una nuova assegnazione al riferimento a, non f , dell'oggetto il cui attributo è "b".

    inserisci qui la descrizione dell'immagine

  6. Quando si chiama modifyReference(Foo c)metodo, cviene creato un riferimento e assegnato l'oggetto con l'attributo "f".

    inserisci qui la descrizione dell'immagine

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

    inserisci qui la descrizione dell'immagine

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


82
+1 Cose interessanti. buoni diagrammi. Ho anche trovato una bella pagina sintetica qui adp-gmbh.ch/php/pass_by_reference.html OK, ammetto che è scritto in PHP, ma è il principio di comprendere la differenza che ritengo importante (e come manipolarla I tuoi bisogni).
DaveM,

5
@ Eng.Fouad È una bella spiegazione, ma se apunta allo stesso oggetto di f(e non ottiene mai la propria copia dell'oggetto fpuntato a), qualsiasi modifica all'oggetto fatta usando adovrebbe cambiare fpure (dal momento che entrambi lavorano con lo stesso oggetto ), quindi ad un certo punto adeve ottenere la propria copia dell'oggetto a cui fpunta.
0x6C38,

14
@MrD quando 'a' punta anche allo stesso oggetto 'f', quindi qualsiasi modifica apportata a quell'oggetto tramite 'a' è anche osservabile tramite 'f', MA NON HA CAMBIATO 'f'. 'f' punta ancora allo stesso oggetto. Puoi cambiare totalmente l'oggetto, ma non puoi mai cambiare ciò a cui punta 'f'. Questo è il problema fondamentale che per qualche ragione alcune persone non riescono a capire.
Mike Braun,

@MikeBraun ... cosa? Ora mi hai confuso: S. Ciò che hai appena scritto non è contrario a ciò che mostra 6. e 7.?
Lavatrice malvagia,

6
Questa è la migliore spiegazione che ho trovato. A proposito, che dire della situazione di base? Ad esempio, l'argomento richiede un tipo int, passa comunque la copia di una variabile int all'argomento?
Allenwang,

732

Questo ti darà alcuni spunti su come funziona davvero Java al punto che nella tua prossima discussione su Java che passa per riferimento o passa per valore, sorridi e basta :-)

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

Il secondo passaggio ricorda che quando si passa un oggetto in un metodo si passa il riferimento all'oggetto e non l'oggetto stesso.

  • Studente : Maestro, questo significa che Java è pass-by-reference?
  • Master : Grasshopper, No.

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

  1. Una variabile contiene i bit che indicano alla JVM come raggiungere l'oggetto referenziato in memoria (Heap).
  2. Quando si passano argomenti a un metodo NON si passa la variabile di riferimento, ma una copia dei bit nella variabile di riferimento . Qualcosa del genere: 3bad086a. 3bad086a rappresenta un modo per raggiungere l'oggetto passato.
  3. Quindi stai solo passando 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 è effettivamente COPIATO e dato al metodo .

Di seguito (non tentare di 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 persona variabile viene creata nella riga # 1 ed è nulla all'inizio.
  • Un nuovo oggetto persona viene creato nella riga n. 2, memorizzato e alla persona variabile viene fornito il riferimento all'oggetto persona. Cioè, il suo indirizzo. Diciamo 3bad086a.
  • La persona variabile che detiene l'indirizzo dell'oggetto viene passata alla funzione nella riga # 3.
  • Nella riga 4 puoi ascoltare il suono del silenzio
  • Controlla il commento sulla riga 5
  • Viene creata una variabile locale del metodo - anotherReferenceToTheSamePersonObject - e quindi arriva la magia nella riga # 6:
    • La variabile / persona di riferimento viene copiata bit per bit e passata a un altroReferenceToTheSamePersonObject all'interno della funzione.
    • Non vengono create nuove istanze di Person.
    • Sia " person " che " anotherReferenceToTheSamePersonObject " mantengono lo stesso valore di 3bad086a.
    • Non provarlo, ma person == anotherReferenceToTheSamePersonObject sarebbe vero.
    • Entrambe le variabili hanno COPIE IDENTICHE del riferimento ed entrambe si riferiscono allo stesso oggetto persona, all'oggetto SAME sull'heap e NON A COPIA.

Un'immagine vale più di mille parole:

Passa per valore

Si noti che le frecce di un altroReferenceToTheSamePersonObject sono dirette verso l'Oggetto e non verso la persona variabile!

Se non l'hai capito, fidati di me e ricorda che è meglio dire che Java è un valore . Bene, passa per valore di riferimento . Vabbè, ancora meglio è il pass-by-copia-del-valore-variabile! ;)

Ora sentiti libero di odiarmi, ma nota che, dato questo, non c'è differenza tra il passaggio di tipi di dati primitivi e Oggetti quando si parla di argomenti di metodo.

Passi 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 primitivo stesso.
  • Se si tratta di un oggetto, i bit conterranno il valore dell'indirizzo che indica alla JVM come raggiungere l'oggetto.

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


La funzione changeName sopra non sarà mai in grado di modificare il contenuto effettivo (i valori di bit) del riferimento passato. In altre parole changeName non può fare in modo che Persona si riferisca a un altro Oggetto.


Ovviamente puoi accorciarlo e dire semplicemente che Java è pass-by-value!


5
Intendi i puntatori? .. Se lo capisco correttamente public void foo(Car car){ ... }, carè locale in fooe contiene la posizione dell'heap dell'oggetto? Quindi, se cambio caril valore di car = new Car(), punterà a un oggetto diverso sull'heap? e se cambio caril valore della proprietà di car.Color = "Red", l'oggetto nell'heap indicato da carverrà modificato. Inoltre, è lo stesso in C #? Per favore rispondi! Grazie!
DPP

11
@domanokz Mi stai uccidendo, per favore non dire più quella parola! ;) Nota che avrei potuto rispondere a questa domanda senza dire anche "riferimento". È un problema di terminologia e la cosa peggiora. Io e Scot abbiamo opinioni diverse su questo purtroppo. Penso che tu abbia capito come funziona in Java, ora puoi chiamarlo pass by-value, by-object-sharing, by-copy-of-the-currency-value o sentirti libero di inventare qualcos'altro! Non mi interessa davvero finché hai capito come funziona e cosa c'è in una variabile di tipo Object: solo un indirizzo di casella postale! ;)
Marsellus Wallace,

9
Sembra che tu abbia appena passato un riferimento ? Ho intenzione di sostenere il fatto che Java è ancora un linguaggio pass-by-reference copiato. Il fatto che si tratti di un riferimento copiato non modifica la terminologia. Entrambi i RIFERIMENTI puntano ancora allo stesso oggetto. Questa è un'argomentazione purista ...
John Strickler,

3
Quindi passa alla linea teorica n. 9 System.out.println(person.getName());che cosa verrà visualizzato? "Tom" o "Jerry"? Questa è l'ultima cosa che mi aiuterà a confondere questa confusione.
TheBrenny,

1
"Il valore di riferimento " è ciò che alla fine mi ha spiegato.
Alexander Shubert,

689

Java è sempre passato per valore, senza eccezioni, mai .

Quindi, come mai qualcuno può essere affatto confuso da questo, e credere che Java sia pass per riferimento, o pensare di avere un esempio di Java che agisce come pass per riferimento? Il punto chiave è che Java non fornisce mai l'accesso diretto ai valori degli oggetti stessi , in nessuna circostanza. L'unico accesso agli oggetti è attraverso un riferimento a quell'oggetto. Poiché gli oggetti Java sono sempre accessibili tramite un riferimento, anziché direttamente, è comune parlare di campi e variabili e argomenti del metodo come oggetti , quando pedanticamente sono solo riferimenti ad oggetti .La confusione deriva da questo (rigorosamente parlando, errato) cambio di nomenclatura.

Quindi, quando si chiama un metodo

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

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

Naturalmente, passare per valore un riferimento a un oggetto assomiglia molto (ed è indistinguibile in pratica da) passare un oggetto per riferimento.


7
Poiché i valori delle primitive sono immutabili (come String), la differenza tra i due casi non è realmente rilevante.
Paŭlo Ebermann,

4
Esattamente. Per quanto si può dire tramite un comportamento JVM osservabile, le primitive potrebbero essere passate per riferimento e potrebbero vivere sull'heap. Non lo fanno, ma in realtà non è osservabile in alcun modo.
Gravità,

6
i primitivi sono immutabili? è nuovo in Java 7?
user85421

4
I puntatori sono immutabili, i primitivi in ​​generale sono mutabili. Anche la stringa non è una primitiva, è un oggetto. Inoltre, la struttura sottostante della stringa è un array mutabile. L'unica cosa immutabile al riguardo è la lunghezza, che è la natura intrinseca degli array.
kingfrito_5005,

3
Questa è un'altra risposta sottolineando la natura in gran parte semantica dell'argomento. La definizione di riferimento fornita in questa risposta renderebbe Java "pass-by-reference". L'autore ammette sostanzialmente quanto nell'ultimo paragrafo affermando che è "indistinguibile in pratica" dal "pass-by-reference". Dubito che OP lo stia chiedendo a causa del desiderio di comprendere l'implementazione di Java ma piuttosto di capire come usare correttamente Java. Se fosse indistinguibile nella pratica, allora non avrebbe senso prendersi cura e anche solo pensarci sarebbe una perdita di tempo.
Loduwijk,

331

Java passa i riferimenti per valore.

Quindi non puoi cambiare il riferimento che viene passato.


27
Ma la cosa che viene ripetuta "non è possibile modificare il valore degli oggetti passati negli argomenti" è chiaramente falsa. Potresti non essere in grado di farli fare riferimento a un oggetto diverso, ma puoi modificarne il contenuto chiamando i loro metodi. IMO significa che perdi tutti i vantaggi delle referenze e non ottieni garanzie aggiuntive.
Timmmm,

34
Non ho mai detto "non puoi cambiare il valore degli oggetti passati negli argomenti". Dirò "Non è possibile modificare il valore del riferimento all'oggetto passato come argomento del metodo", che è una vera affermazione sul linguaggio Java. Ovviamente puoi cambiare lo stato dell'oggetto (purché non sia immutabile).
ScArcher2

20
Tieni presente che non puoi effettivamente passare oggetti in Java; gli oggetti rimangono sul mucchio. I puntatori agli oggetti possono essere passati (che vengono copiati sul frame dello stack per il metodo chiamato). Quindi non cambi mai il valore passato (il puntatore), ma sei libero di seguirlo e cambiare la cosa sull'heap a cui punta. Questo è valore per pass.
Scott Stanchfield,

10
Sembra che tu abbia appena passato un riferimento ? Ho intenzione di sostenere il fatto che Java è ancora un linguaggio pass-by-reference copiato. Il fatto che si tratti di un riferimento copiato non modifica la terminologia. Entrambi i RIFERIMENTI puntano ancora allo stesso oggetto. Questa è un'argomentazione purista ...
John Strickler,

3
Java non passa un oggetto, passa il valore del puntatore all'oggetto. Ciò crea un nuovo puntatore alla posizione di memoria dell'oggetto originale in una nuova variabile. Se si modifica il valore (l'indirizzo di memoria a cui punta) di questa variabile del puntatore in un metodo, il puntatore originale utilizzato nel chiamante del metodo rimane non modificato. Se stai chiamando il parametro come riferimento, il fatto che si tratti di una copia del riferimento originale, non del riferimento originale stesso in modo tale che ora ci siano due riferimenti all'oggetto, significa che è pass-by-value
theferrit32

238

Ho voglia di discutere su "pass-by-reference vs pass-by-value" non è di grande aiuto.

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

Arresto anomalo sullo 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 (nota anche come memoria dinamica) è casuale e disorganizzata. JVM trova spazio ovunque e lo libera, poiché le variabili che lo utilizzano non sono più necessarie.

Va bene. Prima di tutto, le primitive locali vanno in pila. Quindi questo codice:

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

risultati in questo:

primitivi in ​​pila

Quando si dichiara e crea un'istanza di un oggetto. L'oggetto reale va nell'heap. Cosa va in pila? L'indirizzo dell'oggetto sull'heap. I programmatori C ++ lo chiamerebbero un puntatore, ma alcuni sviluppatori Java sono contrari alla parola "puntatore". Qualunque cosa. Basta sapere che l'indirizzo dell'oggetto va nello stack.

Così:

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

ab * 7ch non è uno!

Un array è un oggetto, quindi va anche nell'heap. E gli oggetti nell'array? Ottengono il proprio spazio 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");

fratelli marx

Quindi, cosa viene passato quando chiami un metodo? Se passi un oggetto, quello che stai effettivamente passando è l'indirizzo dell'oggetto. Alcuni potrebbero dire il "valore" dell'indirizzo e alcuni dicono che è solo un riferimento all'oggetto. Questa è la genesi della guerra santa tra i sostenitori del "riferimento" e del "valore". Ciò che chiami non è così importante come 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 viene allocato nell'heap e l'indirizzo della stringa viene archiviato nello stack e viene assegnato l'identificatore hisName, poiché l'indirizzo della seconda stringa è uguale al primo, non viene creata alcuna nuova stringa e non viene assegnato alcun nuovo spazio heap, ma viene creato un nuovo identificatore nello stack. Quindi chiamiamo shout(): viene creato un nuovo frame di stack e viene creato un nuovo identificatore namee assegnato l'indirizzo della stringa già esistente.

la da di da da da da

Quindi, valore, riferimento? Dici "patata".


7
Tuttavia, avresti dovuto seguire un esempio più complesso in cui una funzione sembra modificare una variabile al cui indirizzo ha un riferimento.
Brian Peterson,

34
La gente non "balla attorno al vero problema" di stack vs heap, perché non è questo il vero problema. È un dettaglio di implementazione nella migliore delle ipotesi e decisamente sbagliato nella peggiore. (È possibile che gli oggetti vivano nello stack; google "analisi di escape". E un numero enorme di oggetti contiene primitivi che probabilmente non vivono nello stack.) Il vero problema è esattamente la differenza tra tipi di riferimento e tipi di valore - in particolare, che il valore di una variabile del tipo di riferimento è un riferimento, non l'oggetto a cui fa riferimento.
cHao,

8
È un "dettaglio di implementazione" in quanto Java non è mai richiesto per mostrarti effettivamente dove vive un oggetto in memoria, e in effetti sembra determinato a evitare di perdere quelle informazioni. Potrebbe mettere l'oggetto in pila e non lo sapresti mai. Se ti interessa, ti stai concentrando sulla cosa sbagliata - e in questo caso, ciò significa ignorare il vero problema.
cHao,

10
E in entrambi i casi, "i primitivi vanno in pila" non è corretto. Le variabili locali primitive vanno in pila. (Se non sono stati ottimizzati, ovviamente.) Ma anche le variabili di riferimento locali . E i membri primitivi definiti all'interno di un oggetto vivono ovunque esso viva.
cHao,

9
Accetto i commenti qui. Stack / heap è un problema secondario e non pertinente. Alcune variabili possono trovarsi nello stack, altre sono nella memoria statica (variabili statiche) e molte vivono nell'heap (tutte le variabili dei membri dell'oggetto). NESSUNA di queste variabili può essere passata per riferimento: da un metodo chiamato non è MAI possibile cambiare il valore di una variabile che viene passata come argomento. Pertanto, non esiste un riferimento pass-by in Java.
fishinelle vicinanze di

195

Solo per mostrare il contrasto, confronta i seguenti frammenti di C ++ e Java :

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


7
+1 Vorrei anche aggiungere Dog **objPtrPtrall'esempio C ++, in questo modo possiamo modificare ciò a cui "punta" il puntatore.
Amro,

1
Ma questo non risponde per la domanda data in Java. In realtà, tutto nel metodo Java è Pass By Value, niente di più.
tauitdnmd,

2
Questo esempio dimostra solo che Java usa l'equivalente del puntatore in C no? Dove passa per valore in C sono fondamentalmente di sola lettura? Alla fine, questo intero thread è incomprensibile
amdev


166

Fondamentalmente, la riassegnazione dei parametri Object non influisce sull'argomento, ad es.

private void foo(Object bar) {
    bar = null;
}

public static void main(String[] args) {
    String baz = "Hah!";
    foo(baz);
    System.out.println(baz);
}

stamperà "Hah!"invece di null. Il motivo per cui funziona è perché barè una copia del valore di baz, che è solo un riferimento a "Hah!". Se fosse il riferimento vero e proprio, quindi fooavrebbe ridefinito baza null.


8
Preferirei dire che bar è una copia del riferimento baz (o baz alias), che inizialmente punta allo stesso oggetto.
MaxZoom,

non c'è una leggera differenza tra la classe String e tutte le altre classi?
Mehdi Karamosly,

152

Non posso credere che nessuno abbia menzionato Barbara Liskov. Quando ha progettato CLU nel 1974, ha riscontrato questo stesso problema terminologico e ha inventato il termine chiamata condividendo (noto anche come chiamata per condivisione di oggetti e chiamata per oggetto ) per questo caso specifico di "chiamata per valore dove il valore è un riferimento".


3
Mi piace questa distinzione nella nomenclatura. È un peccato che Java supporti la chiamata condividendo per gli oggetti, ma non la chiamata per valore (come fa C ++). Java supporta la chiamata per valore solo per tipi di dati primitivi e non per tipi di dati compositi.
Derek Mahar,

2
Non credo davvero che avessimo bisogno di un termine in più - è semplicemente pass-by-value per uno specifico tipo di valore. L'aggiunta di "call by primitive" aggiungerebbe qualche chiarimento?
Scott Stanchfield,


Quindi posso passare per riferimento condividendo un oggetto di contesto globale o persino passando un oggetto di contesto che contiene altri riferimenti? Passo ancora per valore, ma almeno ho accesso ai riferimenti che posso modificare e farli puntare a qualcos'altro.
YoYo

1
@Sanjeev: la condivisione call-by-object è un caso speciale di valore pass-by. Tuttavia, molte persone sostengono con veemenza che Java (e linguaggi simili come Python, Ruby, ECMAScript, Smalltalk) sono riferimenti pass-by. Io preferirei chiamarlo passare per valore, anche, ma call-by-oggetto-sharing sembra essere un termine ragionevole che anche le persone che sostengono che esso è non passare per valore in grado di accettare.
Jörg W Mittag

119

Il nocciolo della questione è che la parola riferimento nell'espressione "passa per riferimento" significa qualcosa di completamente diverso dal solito significato della parola riferimento in Java.

Solitamente in riferimento Java si intende un riferimento a un oggetto . Ma i termini tecnici passano per riferimento / valore dalla teoria del linguaggio di programmazione sta parlando di a riferimento alla cella di memoria che contiene la variabile , che è qualcosa di completamente diverso.


7
@Gevorg - Allora che cos'è un "NullPointerException"?
Hot Licks,

5
@Hot: purtroppo un'eccezione denominata prima che Java stabilisse una chiara terminologia. L'eccezione semanticamente equivalente in c # si chiama NullReferenceException.
JacquesB,

4
Mi è sempre sembrato che l'uso del "riferimento" nella terminologia Java sia un problema che ostacola la comprensione.
Hot Licks,

Ho imparato a chiamare questi cosiddetti "puntatori agli oggetti" come un "handle per l'oggetto". Questa ridotta ambiguità.
moronkreacionz,

86

In java tutto è riferimento, quindi quando hai qualcosa come: Point pnt1 = new Point(0,0);Java fa quanto segue:

  1. Crea un nuovo oggetto Point
  2. Crea un nuovo riferimento punto e inizializza quel riferimento al punto (fare riferimento a) sull'oggetto Point precedentemente creato.
  3. Da qui, attraverso la vita dell'oggetto Point, accederai a quell'oggetto tramite riferimento pnt1. Quindi possiamo dire che in Java manipoli l'oggetto attraverso il suo riferimento.

inserisci qui la descrizione dell'immagine

Java non passa argomenti di metodo per riferimento; li passa per valore. Userò esempio da questo sito :

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

Flusso del programma:

Point pnt1 = new Point(0,0);
Point pnt2 = new Point(0,0);

Creazione di due diversi oggetti Point con due diversi riferimenti associati. inserisci qui la descrizione dell'immagine

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

Come previsto, l'output sarà:

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

Su questa linea "passa per valore" entra in gioco ...

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

I riferimenti pnt1e pnt2vengono passati per valore al metodo complicato, il che significa che ora i tuoi riferimenti pnt1e pnt2hanno il loro copiesnome arg1e arg2.So pnt1e arg1 punta allo stesso oggetto. (Lo stesso per ilpnt2 e arg2) inserisci qui la descrizione dell'immagine

Nel trickymetodo:

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

inserisci qui la descrizione dell'immagine

Avanti nel trickymetodo

Point temp = arg1;
arg1 = arg2;
arg2 = temp;

Qui, prima di creare nuovo tempriferimento Point che puntare sullo stesso posto come arg1riferimento. Quindi si sposta il riferimento arg1per puntare allo stesso posto come arg2riferimento. Infine arg2sarà puntare allo stesso posto come temp.

inserisci qui la descrizione dell'immagine

Da qui portata del trickymetodo è andato e non si ha accesso più ai riferimenti: arg1, arg2, temp. Ma la nota importante è che tutto ciò che fai con questi riferimenti quando sono "nella vita" influenzerà in modo permanente l'oggetto su cui sono puntati .

Quindi, dopo aver eseguito il metodo tricky, quando torni amain , hai questa situazione: inserisci qui la descrizione dell'immagine

Quindi ora, l'esecuzione completa del programma sarà:

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

7
In java quando dici "In java tutto è riferimento" intendi che tutti gli oggetti vengono passati per riferimento. I tipi di dati primitivi non vengono passati per riferimento.
Eric,

Sono in grado di stampare il valore scambiato nel metodo principale. nel metodo trickey, aggiungi la seguente istruzione arg1.x = 1; arg1.y = 1; arg2.x = 2; arg2.y = 2;così, come arg1 ora detiene la pnt2 refrence e arg2 detiene ora il riferimento pnt1, quindi, la sua stampaX1: 2 Y1: 2 X2: 1 Y2: 1
Shahid Ghafoor,

85

Java è sempre passato per valore, non per riferimento

Prima di tutto, dobbiamo capire cosa sono il passaggio per valore e il passaggio per riferimento.

Passa per valore significa che stai facendo una copia in memoria del valore del parametro effettivo che viene passato. Questa è una copia del contenuto del parametro effettivo .

Passare per riferimento (chiamato anche passare per indirizzo) significa che viene memorizzata una copia dell'indirizzo del parametro effettivo .

A volte Java può dare l'illusione di passare per riferimento. Vediamo come funziona usando l'esempio seguente:

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

L'output di questo programma è:

changevalue

Comprendiamo passo dopo passo:

Test t = new Test();

Come tutti sappiamo, creerà un oggetto nell'heap e restituirà il valore di riferimento a t. Ad esempio, supponiamo che il valore di t sia 0x100234(non conosciamo l'effettivo valore interno JVM, questo è solo un esempio).

prima illustrazione

new PassByValue().changeValue(t);

Quando passa il riferimento t alla funzione, non passerà direttamente il valore di riferimento effettivo del test oggetto, ma creerà una copia di t e quindi lo passerà alla funzione. Poiché passa per valore , passa una copia della variabile anziché il riferimento effettivo di essa. Poiché abbiamo detto che il valore di t era 0x100234, sia t che f avranno lo stesso valore e quindi indicheranno lo stesso oggetto.

seconda illustrazione

Se cambiate qualcosa nella funzione usando il riferimento f, questo modificherà il contenuto esistente dell'oggetto. Ecco perché abbiamo ottenuto l'outputchangevalue , che viene aggiornato nella funzione.

Per capirlo più chiaramente, considera il seguente esempio:

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

Questo lancerà un NullPointerException? No, perché passa solo una copia del riferimento. Nel caso di passaggio per riferimento, avrebbe potuto generare un NullPointerException, come mostrato di seguito:

terza illustrazione

Spero che questo possa aiutare.


71

Un riferimento è sempre un valore quando rappresentato, indipendentemente dalla lingua utilizzata.

Ottenendo una vista fuori dagli schemi, diamo un'occhiata a Assembly o ad una gestione della memoria di basso livello. A livello di CPU un riferimento a qualsiasi cosa diventa immediatamente un valore se viene scritto in memoria o in uno dei registri della CPU. (Ecco perché puntatore è una buona definizione. È un valore, che ha uno scopo allo stesso tempo).

I dati in memoria hanno una posizione e in quella posizione c'è un valore (byte, parola, qualunque cosa). In Assembly abbiamo una soluzione conveniente per assegnare un Nome a una determinata Posizione (nota anche come variabile), ma durante la compilazione del codice, l'assemblatore sostituisce semplicemente Nome con la posizione designata proprio come il browser sostituisce i nomi di dominio con indirizzi IP.

Fino al centro è tecnicamente impossibile passare un riferimento a qualsiasi cosa in qualsiasi lingua senza rappresentarlo (quando diventa immediatamente un valore).

Diciamo che abbiamo una variabile Foo, la sua posizione è al 47 ° byte in memoria e il suo valore è 5. Abbiamo un'altra variabile Ref2Foo che è al 223 ° byte in memoria, e il suo valore sarà 47. Questo Ref2Foo potrebbe essere una variabile tecnica , non esplicitamente creato dal programma. Se guardi solo 5 e 47 senza altre informazioni, vedrai solo due valori . Se li usi come riferimenti, per raggiungerci 5dobbiamo viaggiare:

(Name)[Location] -> [Value at the Location]
---------------------
(Ref2Foo)[223]  -> 47
(Foo)[47]       -> 5

Ecco come funzionano i jump-table.

Se vogliamo chiamare un metodo / funzione / procedura con il valore di Foo, ci sono alcuni modi possibili per passare la variabile al metodo, a seconda della lingua e delle sue diverse modalità di invocazione del metodo:

  1. 5 viene copiato in uno dei registri della CPU (es. EAX).
  2. 5 ottiene PUSHd nello stack.
  3. 47 viene copiato in uno dei registri della CPU
  4. 47 PUSHd allo stack.
  5. 223 viene copiato in uno dei registri della CPU.
  6. 223 ottiene PUSHd nello stack.

In ogni caso sopra un valore - una copia di un valore esistente - è stato creato, ora è il metodo di ricezione a gestirlo. Quando si scrive "Foo" all'interno del metodo, questo viene letto da EAX, o automaticamente dereferenziato , o doppio riferimento, il processo dipende da come funziona la lingua e / o da ciò che il tipo di Foo impone. Questo è nascosto allo sviluppatore fino a quando non aggira il processo di dereferenziazione. Quindi un riferimento è un valore quando rappresentato, perché un riferimento è un valore che deve essere elaborato (a livello di lingua).

Ora abbiamo passato Foo al metodo:

  • nel caso 1. e 2. se si cambia Foo (Foo = 9 ), ha effetto solo sull'ambito locale in quanto si dispone di una copia del Valore. Dall'interno del metodo non possiamo nemmeno determinare dove si trovasse in memoria il Foo originale.
  • nel caso in cui 3. e 4. se si utilizza costrutti del linguaggio di default e il cambiamento Foo ( Foo = 11), potrebbe cambiare Foo globale (dipende dalla lingua, vale a dire. Java o come quella di Pascal procedure findMin(x, y, z: integer;var m: integer); ). Tuttavia, se la lingua ti consente di aggirare il processo di dereferenza, puoi cambiare 47, per esempio 49. A quel punto Foo sembra essere stato modificato se lo leggi, perché hai cambiato il puntatore locale . E se dovessi modificare questo Foo all'interno del metodo ( Foo = 12) probabilmente FUBAR l'esecuzione del programma (aka. Segfault) perché scriverai su una memoria diversa da quella prevista, puoi persino modificare un'area destinata a essere eseguibile il programma e la sua scrittura modificheranno il codice in esecuzione (Foo non è ora disponibile47 ). MA il valore di Foo di47non è cambiato a livello globale, solo quello all'interno del metodo, perché 47era anche una copia del metodo.
  • nei casi 5. e 6. se si modifica 223all'interno del metodo crea lo stesso caos di 3. o 4. (un puntatore, che punta a un valore ora non valido, che viene nuovamente usato come puntatore) ma questo è ancora un locale problema, poiché 223 è stato copiato . Tuttavia, se sei in grado di dereferenziare Ref2Foo(cioè 223), raggiungere e modificare il valore puntato 47, diciamo, 49influenzerà Foo a livello globale , perché in questo caso i metodi ne hanno ottenuto una copia 223 ma il riferimento 47esiste solo una volta, e cambiandolo to 49porterà ogni Ref2Foodoppio dereferenziamento a un valore errato.

Spuntando su dettagli insignificanti, anche le lingue che passano per riferimento passeranno i valori alle funzioni, ma quelle funzioni sanno che devono usarlo per scopi di dereferenziazione. Questo valore di riferimento come valore è appena nascosto al programmatore perché è praticamente inutile e la terminologia è solo riferimento per passaggio .

Anche il rigoroso valore di passaggio è inutile, significherebbe che un array da 100 Mbyte dovrebbe essere copiato ogni volta che chiamiamo un metodo con l'array come argomento, quindi Java non può essere rigorosamente pass-by-value. Ogni lingua passerebbe un riferimento a questo enorme array (come valore) e impiega un meccanismo di copia su scrittura se tale array può essere modificato localmente all'interno del metodo o consente al metodo (come fa Java) di modificare l'array a livello globale (da vista del chiamante) e alcune lingue consentono di modificare il valore del riferimento stesso.

Quindi, in breve e nella stessa terminologia di Java, Java è pass-by-value dove può essere value : un valore reale o un valore che è una rappresentazione di un riferimento .


1
In una lingua con riferimento pass-by, la cosa che viene passata (il riferimento) è effimera; il destinatario non è "supposto" di copiarlo. In Java, il passaggio di una matrice è un "identificatore di oggetto" - equivalente a una distinta di carta che dice "Oggetto # 24601", quando il 24601 ° oggetto costruito era una matrice. Il destinatario può copiare "Oggetto # 24601" dove vuole e chiunque abbia un foglietto che dice "Oggetto # 24601" può fare tutto ciò che vuole con uno qualsiasi degli elementi dell'array. Il modello di bit che viene passato in realtà non direbbe "Oggetto # 24601", ovviamente, ma ...
Supercat

... il punto chiave è che il destinatario dell'ID oggetto che identifica l'array può archiviare quell'ID oggetto dove vuole e darlo a chi vuole, e qualsiasi destinatario sarebbe in grado di accedere o modificare l'array ogni volta che lo desidera vuole. Al contrario, se una matrice fosse passata per riferimento in una lingua come Pascal che supporta tali cose, il metodo chiamato potrebbe fare ciò che voleva con la matrice, ma non potrebbe memorizzare il riferimento in modo tale da consentire al codice di modificare la matrice dopo che è tornato.
supercat

Infatti, in Pascal puoi ottenere l'indirizzo di ogni variabile, che sia passata per riferimento o copiata localmente, addrnon tipizzata, @con tipo, e puoi modificare la variabile referenziata in seguito (tranne quelle copiate localmente). Ma non vedo il motivo per cui lo faresti. Quella distinta di carta nel tuo esempio (Oggetto # 24601) è un riferimento, il suo scopo è di aiutare a trovare l'array in memoria, non contiene alcun dato dell'array in sé. Se riavvii il programma, lo stesso array potrebbe ottenere un ID oggetto diverso anche se il suo contenuto sarà lo stesso di quello dell'esecuzione precedente.
Karatedog,

Avevo pensato che l'operatore "@" non facesse parte dello standard Pascal, ma era stato implementato come estensione comune. Fa parte dello standard? Il mio punto era che in un linguaggio con vero pass-by-ref e nessuna capacità di costruire un puntatore non effimero su un oggetto effimero, codice che contiene l'unico riferimento a un array, in qualsiasi punto dell'universo, prima di passare l'array il riferimento può sapere che, a meno che il destinatario "imbroglia", continuerà a conservare l'unico riferimento successivo. L'unico modo sicuro per realizzarlo in Java sarebbe costruire un oggetto temporaneo ...
supercat

... che incapsula AtomicReferencee non espone né il riferimento né il suo target, ma include invece metodi per fare cose al target; una volta restituito il codice a cui l'oggetto è stato passato, il AtomicReference[a cui il suo creatore ha mantenuto un riferimento diretto] dovrebbe essere invalidato e abbandonato. Ciò fornirebbe la semantica corretta, ma sarebbe lento e icky.
supercat

70

Java è una chiamata per valore

Come funziona

  • Passi sempre una copia dei bit del valore del riferimento!

  • Se si tratta di un tipo di dati primitivo, questi bit contengono il valore del tipo di dati primitivo stesso, ecco perché se cambiamo il valore dell'intestazione all'interno del metodo, ciò non riflette le modifiche all'esterno.

  • Se si tratta di un tipo di dati oggetto come Foo foo = new Foo (), in questo caso la copia dell'indirizzo dell'oggetto passa come collegamento al file, supponiamo di avere un file di testo abc.txt su C: \ desktop e supponiamo di creare un collegamento a lo stesso file e inseriscilo in C: \ desktop \ abc-shortcut così quando accedi al file da C: \ desktop \ abc.txt e scrivi 'Stack Overflow' e chiudi il file e di nuovo apri il file dal collegamento quindi scrivi "è la più grande comunità online che i programmatori possano imparare", quindi la modifica totale del file sarà il "Stack Overflow è la più grande comunità online che i programmatori possano imparare"che significa che non importa da dove apri il file, ogni volta che accedevamo allo stesso file, qui possiamo presumereFoo come file e supponiamo foo memorizzato all'indirizzo 123hd7h (indirizzo originale come C: \ desktop \ abc.txt ) e 234jdid (indirizzo copiato come C: \ desktop \ abc-shortcut che contiene effettivamente l'indirizzo originale del file all'interno). Quindi, per una migliore comprensione, crea un file di scorciatoia e senti ..


66

Ci sono già ottime risposte che riguardano questo. Volevo dare un piccolo contributo condividendo un esempio molto semplice (che verrà compilato) contrastando i comportamenti tra Pass-by-reference in c ++ e Pass-by-value in Java.

Alcuni punti:

  1. Il termine "riferimento" è sovraccarico di due significati separati. In Java significa semplicemente un puntatore, ma nel contesto di "Passa per riferimento" significa un handle per la variabile originale che è stata passata.
  2. Java è Pass-by-value . Java è un discendente di C (tra le altre lingue). Prima di C, diverse lingue precedenti (ma non tutte) come FORTRAN e COBOL supportavano PBR, ma C no. PBR ha permesso a queste altre lingue di apportare modifiche alle variabili passate all'interno delle routine secondarie. Al fine di realizzare la stessa cosa (cioè cambiare i valori delle variabili all'interno delle funzioni), i programmatori C hanno passato i puntatori alle variabili in funzioni. I linguaggi ispirati a C, come Java, hanno preso in prestito questa idea e continuano a passare il puntatore ai metodi come ha fatto C, tranne che Java chiama i suoi puntatori Riferimenti. Ancora una volta, questo è un uso diverso della parola "Riferimento" rispetto a "Passa per riferimento".
  3. C ++ consente il pass-by-reference dichiarando un parametro di riferimento usando il carattere "&" (che risulta essere lo stesso carattere usato per indicare "l'indirizzo di una variabile" in C e C ++). Ad esempio, se passiamo un puntatore per riferimento, il parametro e l'argomento non puntano solo allo stesso oggetto. Piuttosto, sono la stessa variabile. Se uno viene impostato su un indirizzo diverso o su null, anche l'altro.
  4. Nell'esempio C ++ sotto sto passando un puntatore a una stringa terminata null per riferimento . E nell'esempio Java seguente sto passando un riferimento Java a una stringa (di nuovo, lo stesso di un puntatore a una stringa) in base al valore. Notare l'output nei commenti.

C ++ passa per esempio di riferimento:

using namespace std;
#include <iostream>

void change (char *&str){   // the '&' makes this a reference parameter
    str = NULL;
}

int main()
{
    char *str = "not Null";
    change(str);
    cout<<"str is " << str;      // ==>str is <null>
}

Java passa "un riferimento Java" per esempio di valore

public class ValueDemo{

    public void change (String str){
        str = null;
    }

     public static void main(String []args){
        ValueDemo vd = new ValueDemo();
        String str = "not null";
        vd.change(str);
        System.out.println("str is " + str);    // ==> str is not null!!
                                                // Note that if "str" was
                                                // passed-by-reference, it
                                                // WOULD BE NULL after the
                                                // call to change().
     }
}

MODIFICARE

Diverse persone hanno scritto commenti che sembrano indicare che o non stanno guardando i miei esempi o non ottengono l'esempio c ++. Non sono sicuro di dove sia la disconnessione, ma indovinare l'esempio c ++ non è chiaro. Sto pubblicando lo stesso esempio in pascal perché penso che il pass-by-reference sia più pulito in pascal, ma potrei sbagliarmi. Potrei solo confondere di più le persone; Spero di no.

In pascal, i parametri passati per riferimento sono chiamati "parametri var". Nella procedura setToNil di seguito, notare la parola chiave 'var' che precede il parametro 'ptr'. Quando un puntatore viene passato a questa procedura, verrà passato per riferimento . Nota il comportamento: quando questa procedura imposta ptr su zero (ovvero Pascal parla per NULL), imposterà l'argomento su zero - non puoi farlo in Java.

program passByRefDemo;
type 
   iptr = ^integer;
var
   ptr: iptr;

   procedure setToNil(var ptr : iptr);
   begin
       ptr := nil;
   end;

begin
   new(ptr);
   ptr^ := 10;
   setToNil(ptr);
   if (ptr = nil) then
       writeln('ptr seems to be nil');     { ptr should be nil, so this line will run. }
end.

MODIFICA 2

Alcuni estratti da "THE Java Programming Language" di Ken Arnold, James Gosling (il ragazzo che ha inventato Java) e David Holmes, capitolo 2, sezione 2.6.5

Tutti i parametri ai metodi vengono passati "per valore" . In altre parole, i valori delle variabili dei parametri in un metodo sono copie dell'invocatore specificato come argomenti.

Continua a fare lo stesso punto per quanto riguarda gli oggetti. . .

Si noti che quando il parametro è un riferimento a un oggetto, è il riferimento a un oggetto, non l'oggetto stesso, che viene passato "per valore" .

E verso la fine della stessa sezione fa una dichiarazione più ampia sul fatto che java è solo un passaggio di valore e mai un riferimento.

Il linguaggio di programmazione Java non passa oggetti per riferimento; si passa riferimenti agli oggetti per valore . Poiché due copie dello stesso riferimento si riferiscono allo stesso oggetto reale, le modifiche apportate attraverso una variabile di riferimento sono visibili attraverso l'altra. Esiste esattamente un parametro che passa la modalità: passa per valore e aiuta a mantenere le cose semplici.

Questa sezione del libro ha una grande spiegazione del passaggio di parametri in Java e della distinzione tra pass-by-reference e pass-by-value ed è del creatore di Java. Incoraggerei chiunque a leggerlo, soprattutto se non sei ancora convinto.

Penso che la differenza tra i due modelli sia molto sottile e, a meno che tu non abbia fatto la programmazione in cui hai effettivamente utilizzato il pass-by-reference, è facile perdere i due modelli.

Spero che questo risolva il dibattito, ma probabilmente non lo farà.

MODIFICA 3

Potrei essere un po 'ossessionato da questo post. Probabilmente perché ritengo che i produttori di Java abbiano inavvertitamente diffuso disinformazione. Se invece di usare la parola "riferimento" per i puntatori avessero usato qualcos'altro, diciamo Dingleberry, non ci sarebbero stati problemi. Si potrebbe dire "Java passa i mirtilli rossi per valore e non per riferimento", e nessuno sarebbe confuso.

Questo è il motivo per cui solo gli sviluppatori Java hanno problemi con questo. Guardano la parola "riferimento" e pensano di sapere esattamente cosa significhi, quindi non si preoccupano nemmeno di considerare l'argomento opposto.

Ad ogni modo, ho notato un commento in un post più vecchio, che ha creato un'analogia con i palloncini che mi è davvero piaciuta. Tanto che ho deciso di incollare insieme alcune clip art per realizzare una serie di cartoni animati per illustrare il punto.

Passare un riferimento per valore: le modifiche al riferimento non si riflettono nell'ambito del chiamante, ma lo sono le modifiche all'oggetto. Questo perché il riferimento viene copiato, ma sia l'originale che la copia fanno riferimento allo stesso oggetto. Passare i riferimenti agli oggetti per valore

Passa per riferimento : non esiste una copia del riferimento. Il riferimento singolo è condiviso sia dal chiamante che dalla funzione chiamata. Eventuali modifiche al riferimento o ai dati dell'oggetto si riflettono nell'ambito del chiamante. Passa per riferimento

MODIFICA 4

Ho visto post su questo argomento che descrivono l'implementazione a basso livello del passaggio dei parametri in Java, che ritengo sia eccezionale e molto utile perché rende concreta un'idea astratta. Tuttavia, per me la domanda è più sul comportamento descritto nelle specifiche del linguaggio che sull'implementazione tecnica del comportamento. Questo è un estratto dalle specifiche del linguaggio Java, sezione 8.4.1 :

Quando viene invocato il metodo o il costruttore (§15.12), i valori delle espressioni degli argomenti effettivi inizializzano le variabili dei parametri appena create, ciascuna del tipo dichiarato, prima dell'esecuzione del corpo del metodo o del costruttore. L'identificatore visualizzato in DeclaratorId può essere utilizzato come nome semplice nel corpo del metodo o del costruttore per fare riferimento al parametro formale.

Ciò significa che java crea una copia dei parametri passati prima di eseguire un metodo. Come la maggior parte delle persone che hanno studiato compilatori al college, ho usato "The Dragon Book", che è IL libro dei compilatori. Ha una buona descrizione di "Chiamata per valore" e "Chiamata per riferimento" nel Capitolo 1. La descrizione di Chiamata per valore corrisponde esattamente alle specifiche Java.

Quando studiavo compilatori, negli anni '90, ho usato la prima edizione del libro del 1986, che ha preceduto Java di circa 9 o 10 anni. Tuttavia, mi sono appena imbattuto in una copia della seconda edizione del 2007 che in realtà menziona Java! La sezione 1.6.6, intitolata "Meccanismi di passaggio dei parametri", descrive i passaggi dei parametri piuttosto bene. Ecco un estratto sotto l'intestazione "Call-by-value" che menziona Java:

In call-by-value, il parametro effettivo viene valutato (se è un'espressione) o copiato (se è una variabile). Il valore viene inserito nella posizione appartenente al corrispondente parametro formale della procedura chiamata. Questo metodo viene utilizzato in C e Java ed è un'opzione comune in C ++, così come nella maggior parte degli altri linguaggi.


4
Onestamente, puoi semplificare questa risposta dicendo che Java è pass per valore solo per i tipi primitivi. Tutto ciò che eredita da Object viene effettivamente passato per riferimento, dove il riferimento è il puntatore che stai passando.
Scuba Steve

1
@JuanMendes, ho appena usato C ++ come esempio; Potrei darti esempi in altre lingue. Il termine "Passa per riferimento" esisteva molto prima dell'esistenza di C ++. È un termine da manuale con una definizione molto specifica. E per definizione, Java non è un riferimento. Se lo desideri, puoi continuare a utilizzare il termine in questo modo, ma il tuo utilizzo non sarà coerente con la definizione del libro di testo. Non è solo un puntatore a un puntatore. È un costrutto fornito dalla lingua per consentire "Passa per riferimento". Per favore, dai un'occhiata al mio esempio, ma per favore non crederci sulla parola e cercala tu stesso.
Sanjeev,

2
@AutomatedMike Penso che anche descrivere Java come "pass-by-reference" sia fuorviante, i loro riferimenti non sono altro che puntatori. Dato che Sanjeev ha scritto valore, riferimento e puntatore sono termini da manuale che hanno il loro significato indipendentemente dall'uso dei creatori Java.
Jędrzej Dudkiewicz,

1
@ArtanisZeratul il punto è sottile, ma non complicato. Dai un'occhiata al cartone animato che ho pubblicato. Ho pensato che fosse abbastanza semplice da seguire. Che Java sia Pass-by-value non è solo un'opinione. È vero per la definizione da manuale del valore pass-by. Inoltre, "le persone che dicono sempre che passa per valore" includono scienziati informatici come James Gosling, il creatore di Java. Si prega di vedere le citazioni dal suo libro sotto "Modifica 2" nel mio post.
Sanjeev,

1
Che grande risposta, "passa per riferimento" non esiste in Java (e in altre lingue come JS).
newfolder,

56

Per quanto ne so, Java conosce solo chiamate per valore. Ciò significa che per i tipi di dati primitivi lavorerai con una copia e per gli oggetti lavorerai con una copia del riferimento agli oggetti. Comunque penso che ci siano alcune insidie; ad esempio, questo non funzionerà:

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

Questo popolerà Hello World e non World Hello perché nella funzione di swap si utilizzano copie che non hanno alcun impatto sui riferimenti nella schermata principale. Ma se i tuoi oggetti non sono immutabili, puoi cambiarli ad esempio:

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

Questo popolerà Hello World dalla riga di comando. Se cambi StringBuffer in String, si produrrà solo Hello perché String è immutabile. Per esempio:

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

Tuttavia potresti creare un wrapper per String come questo che lo renderebbe in grado di usarlo con Stringhe:

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

modifica: credo che questo sia anche il motivo per usare StringBuffer quando si tratta di "aggiungere" due stringhe perché è possibile modificare l'oggetto originale che non è possibile con oggetti immutabili come String.


+1 per il test di scambio - probabilmente il modo più semplice e comprensibile per distinguere tra passaggio per riferimento e passaggio di un riferimento per valore . Se puoi facilmente scrivere una funzione swap(a, b)che (1) scambia ae bdal POV del chiamante, (2) è di tipo agnostico nella misura consentita dalla tipizzazione statica (il significato usarlo con un altro tipo non richiede altro che cambiare i tipi dichiarati di ae b) e (3) non richiede al chiamante di passare esplicitamente un puntatore o un nome, quindi la lingua supporta il passaggio per riferimento.
cHao,

"..per i tipi di dati primitivi lavorerai con una copia e per gli oggetti lavorerai con una copia del riferimento agli oggetti" - scritto perfettamente!
Raúl,

55

No, non è un riferimento.

Java viene passato per valore in base alle specifiche del linguaggio Java:

Quando viene invocato il metodo o il costruttore (§15.12), i valori delle espressioni degli argomenti effettivi inizializzano le variabili dei parametri appena create , ciascuna del tipo dichiarato, prima dell'esecuzione del corpo del metodo o del costruttore. L'identificatore visualizzato in DeclaratorId può essere utilizzato come nome semplice nel corpo del metodo o del costruttore per fare riferimento al parametro formale .


52

Vorrei provare a spiegare la mia comprensione con l'aiuto di quattro esempi. Java è pass-by-value e non pass-by-reference

/ **

Passa per valore

In Java, tutti i parametri vengono passati per valore, ovvero l'assegnazione di un argomento del metodo non è visibile al chiamante.

* /

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

Risultato

output : output
value : Nikhil
valueflag : false

Esempio 2:

/ ** * * Passa per valore * * /

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

Risultato

output : output
value : Nikhil
valueflag : false

Esempio 3:

/ ** Questo 'Passa per valore ha la sensazione di' Passa per riferimento '

Alcune persone dicono che i tipi primitivi e "String" sono "passa per valore" e gli oggetti sono "passa per riferimento".

Ma da questo esempio, possiamo capire che è infatti solo passaggio per valore, tenendo presente che qui stiamo passando il riferimento come valore. cioè: il riferimento viene passato per valore. Ecco perché sono in grado di cambiare e rimane valido dopo l'ambito locale. Ma non possiamo cambiare il riferimento effettivo al di fuori dell'ambito originale. ciò significa che è dimostrato dal prossimo esempio di 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";
    }
}

Risultato

output : output
student : Student [id=10, name=Anand]

Esempio 4:

/ **

Oltre a quanto menzionato in Esempio3 (PassByValueObjectCase1.java), non possiamo modificare il riferimento effettivo al di fuori dell'ambito originale. "

Nota: non sto incollando il codice per private class Student. La definizione di classe per Studentè la stessa di Esempio3.

* /

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

}

Risultato

output : output
student : Student [id=10, name=Nikhil]

49

Non è mai possibile passare per riferimento in Java e uno dei modi ovvi è quando si desidera restituire più di un valore da una chiamata al metodo. Considera il seguente bit di codice in 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 volte vuoi usare lo stesso modello in Java, ma non puoi; almeno non direttamente. Invece potresti fare qualcosa del genere:

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

Come spiegato nelle risposte precedenti, in Java si passa un puntatore all'array come valore in getValues. Ciò è sufficiente, poiché il metodo modifica quindi l'elemento array e, per convenzione, ti aspetti che l'elemento 0 contenga il valore restituito. Ovviamente puoi farlo in altri modi, come strutturare il tuo codice in modo che ciò non sia necessario o costruire una classe che può contenere il valore restituito o consentirne l'impostazione. Ma il modello semplice disponibile in C ++ sopra non è disponibile in Java.


48

Ho pensato di contribuire con questa risposta per aggiungere ulteriori dettagli dalle Specifiche.

Innanzitutto, qual è la differenza tra passaggio per riferimento e passaggio per valore?

Passare per riferimento significa che il parametro delle funzioni chiamate sarà lo stesso dell'argomento passato dei chiamanti (non il valore, ma l'identità - la variabile stessa).

Passa per valore significa che il parametro delle funzioni chiamate sarà una copia dell'argomento passato dei chiamanti.

O da Wikipedia, in materia di pass-by-reference

Nella valutazione call-by-reference (nota anche come pass-by-reference), una funzione riceve un riferimento implicito a una variabile utilizzata come argomento, anziché una copia del suo valore. Ciò significa in genere che la funzione può modificare (ovvero assegnare a) la variabile utilizzata come argomento, qualcosa che verrà visto dal suo chiamante.

E in tema di pass-by-value

In call-by-value, viene valutata l'espressione dell'argomento e il valore risultante è associato alla variabile corrispondente nella funzione [...]. Se la funzione o la procedura è in grado di assegnare valori ai suoi parametri, viene assegnata solo la sua copia locale [...].

In secondo luogo, dobbiamo sapere cosa utilizza Java nelle sue invocazioni di metodo. Le specifiche del linguaggio Java stati delle

Quando viene invocato il metodo o il costruttore (§15.12), i valori delle espressioni degli argomenti effettivi inizializzano le variabili dei parametri appena create , ciascuna del tipo dichiarato, prima dell'esecuzione del corpo del metodo o del costruttore.

Quindi assegna (o lega) il valore dell'argomento alla variabile del parametro corrispondente.

Qual è il valore dell'argomento?

Consideriamo i tipi di riferimento, gli stati di specifica della macchina virtuale Java

Esistono tre tipi di tipi di riferimento : tipi di classe, tipi di array e tipi di interfaccia. I loro valori sono riferimenti a istanze di classe, array o istanze di classe create dinamicamente o array che implementano interfacce, rispettivamente.

Il linguaggio Java Specification afferma anche

I valori di riferimento (spesso solo riferimenti) sono puntatori a questi oggetti e uno speciale riferimento null, che non fa riferimento a nessun oggetto.

Il valore di un argomento (di un certo tipo di riferimento) è un puntatore a un oggetto. Si noti che una variabile, un'invocazione di un metodo con un tipo restituito di tipo di riferimento e un'espressione di creazione dell'istanza (new ... ) si risolvono tutti in un valore del tipo di riferimento.

Così

public void method (String param) {}
...
String var = new String("ref");
method(var);
method(var.toString());
method(new String("ref"));

tutto legano il valore di un riferimento ad un Stringesempio per il parametro appena creata del metodo, param. Questo è esattamente ciò che descrive la definizione di pass-by-value. Come tale, Java è pass-by-value .

Il fatto che sia possibile seguire il riferimento per invocare un metodo o accedere a un campo dell'oggetto a cui si fa riferimento è completamente irrilevante per la conversazione.La definizione di pass-by-reference era

Ciò significa in genere che la funzione può modificare (ovvero assegnare a) la variabile utilizzata come argomento, qualcosa che verrà visto dal suo chiamante.

In Java, modificare la variabile significa riassegnarla. In Java, se si riassegnava la variabile all'interno del metodo, passerebbe inosservata al chiamante. La modifica dell'oggetto a cui fa riferimento la variabile è un concetto completamente diverso.


I valori primitivi sono anche definiti nelle Specifiche della macchina virtuale Java, qui . Il valore del tipo è il corrispondente valore in virgola mobile o integrale, codificato in modo appropriato (8, 16, 32, 64, ecc. Bit).


42

In Java vengono passati solo i riferimenti e passati per valore:

Gli argomenti Java vengono tutti passati per valore (il riferimento viene copiato quando utilizzato dal metodo):

Nel caso di tipi primitivi, il comportamento Java è semplice: il valore viene copiato in un'altra istanza del tipo primitivo.

Nel caso di oggetti, questo è lo stesso: le variabili oggetto sono puntatori (secchi) che contengono solo l' indirizzo dell'oggetto che è stato creato utilizzando la "nuova" parola chiave e vengono copiati come tipi primitivi.

Il comportamento può apparire diverso dai tipi primitivi: poiché la variabile oggetto copiata contiene lo stesso indirizzo (allo stesso oggetto). Contenuto / membri dell'oggetto potrebbero comunque essere modificati all'interno di un metodo e successivamente accedere all'esterno, dando l'illusione che l'oggetto (contenente) stesso sia stato passato per riferimento.

Gli oggetti "String" sembrano essere un buon controesempio alla leggenda urbana che afferma che "Gli oggetti vengono passati per riferimento":

In effetti, usando un metodo, non sarai mai in grado di aggiornare il valore di una stringa passata come argomento:

Un oggetto String, contiene i caratteri di un array dichiarato final che non può essere modificato. Solo l'indirizzo dell'oggetto potrebbe essere sostituito da un altro usando "nuovo". L'uso di "nuovo" per aggiornare la variabile non consentirà l'accesso all'oggetto dall'esterno, poiché la variabile è stata inizialmente passata per valore e copiata.


Quindi è di Rif per quanto riguarda gli oggetti e di Val per quanto riguarda i primitivi?
Mox,

@mox, per favore, leggi: Gli oggetti non vengono passati per riferimento, questo è un ledgend: String a = new String ("invariato");
user1767316

1
@Aaron "Passa per riferimento" non significa "passa un valore che è un membro di un tipo chiamato" riferimento "in Java". I due usi di "riferimento" significano cose diverse.
Philipxy,

2
Risposta piuttosto confusa. Il motivo per cui non è possibile modificare un oggetto stringa in un metodo, che viene passato per riferimento, è che gli oggetti stringa sono immutabili in base alla progettazione e non è possibile eseguire operazioni simili strParam.setChar ( i, newValue ). Detto questo, le stringhe, come qualsiasi altra cosa, vengono passate per valore e, poiché String è un tipo non primitivo, quel valore è un riferimento all'oggetto creato con new, e puoi verificarlo usando String.intern () .
zakmck,

1
Invece, non è possibile modificare una stringa tramite param = "un'altra stringa" (equivalente alla nuova stringa ("un'altra stringa")) perché il valore di riferimento di param (che ora punta a "un'altra stringa") non può tornare dal metodo corpo. Ma questo è vero per qualsiasi altro oggetto, la differenza è che, quando l'interfaccia di classe lo consente, è possibile eseguire param.changeMe () e l'oggetto di livello superiore a cui viene fatto riferimento cambierà perché param lo sta puntando, nonostante il valore di riferimento di param stesso (la posizione indirizzata, in termini di C) non può tornare indietro dal metodo.
zakmck,

39

La distinzione, o forse solo il modo in cui mi ricordo quando ero sotto la stessa impressione del poster originale è questa: Java è sempre un valore. Tutti gli oggetti (in Java, qualsiasi cosa tranne le primitive) in Java sono riferimenti. Questi riferimenti vengono passati per valore.


2
Trovo la tua penultima frase molto fuorviante. Non è vero che "tutti gli oggetti in Java sono riferimenti". Sono solo i riferimenti a quegli oggetti che sono riferimenti.
Dawood ibn Kareem,

37

Come molte persone hanno già detto prima, Java è sempre pass-by-value

Ecco un altro esempio che ti aiuterà a capire la differenza ( il classico esempio di scambio ):

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

stampe:

Prima: a = 2, b = 3
Dopo: a = 2, b = 3

Ciò accade perché iA e iB sono nuove variabili di riferimento locali che hanno lo stesso valore dei riferimenti passati (indicano rispettivamente a e b). Quindi, il tentativo di cambiare i riferimenti di iA o iB cambierà solo nell'ambito locale e non al di fuori di questo metodo.


32

Java ha solo un passaggio per valore. Un esempio molto semplice per convalidare questo.

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

4
Questo è il modo più chiaro e semplice per vedere che Java passa per valore. Il valore di obj( null) è stato passato a init, non un riferimento a obj.
David Schwartz,

31

Lo considero sempre "passaggio per copia". È una copia del valore sia esso primitivo o di riferimento. Se è una primitiva è una copia dei bit che sono il valore e se è un oggetto è una copia del riferimento.

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

uscita di java PassByCopy:

name = Maxx
name = Fido

Le classi di wrapper e le stringhe primitive sono immutabili, quindi qualsiasi esempio che utilizza tali tipi non funzionerà come gli altri tipi / oggetti.


5
"passa per copia" è ciò che significa valore per passaggio .
TJ Crowder,

@TJCrowder. "Passa per copia" può essere un modo più appropriato di esprimere lo stesso concetto per scopi di Java: perché semanticamente rende molto difficile dare un'idea di un'idea simile al passaggio per riferimento, che è ciò che vuoi in Java.
mike rodent,

@mikerodent - Mi dispiace, non l'ho seguito. :-) "Pass-by-value" e "pass-by-reference" sono i termini dell'arte corretti per questi concetti. Dovremmo usarli (fornendo le loro definizioni quando le persone ne hanno bisogno) piuttosto che inventarne di nuove. Sopra ho detto "passaggio per copia" è ciò che significa "passaggio per valore", ma in realtà non è chiaro cosa significhi "passaggio per copia": una copia di cosa? Il valore? (Ad esempio, riferimento all'oggetto.) L'oggetto stesso? Quindi mi attengo ai termini dell'arte. :-)
TJ Crowder,

28

A differenza di altre lingue, Java non consente di scegliere tra valore per passaggio e riferimento per passaggio: tutti gli argomenti vengono passati per valore. Una chiamata di metodo può passare due tipi di valori a un metodo: copie di valori primitivi (ad es. Valori di int e double) e copie di riferimenti ad oggetti.

Quando un metodo modifica un parametro di tipo primitivo, le modifiche al parametro non hanno alcun effetto sul valore dell'argomento originale nel metodo chiamante.

Quando si tratta di oggetti, gli oggetti stessi non possono essere passati ai metodi. Quindi passiamo il riferimento (indirizzo) dell'oggetto. Possiamo manipolare l'oggetto originale usando questo riferimento.

Come Java crea e memorizza oggetti: quando creiamo un oggetto, memorizziamo l'indirizzo dell'oggetto in una variabile di riferimento. Analizziamo la seguente dichiarazione.

Account account1 = new Account();

"Account account1" è il tipo e il nome della variabile di riferimento, "=" è l'operatore di assegnazione, "nuovo" richiede la quantità di spazio richiesta dal sistema. Il costruttore a destra della parola chiave new che crea l'oggetto viene chiamato implicitamente dalla parola chiave new. L'indirizzo dell'oggetto creato (risultato del valore corretto, che è un'espressione chiamata "espressione di creazione dell'istanza di classe") viene assegnato al valore sinistro (che è una variabile di riferimento con un nome e un tipo specificati) utilizzando l'operatore di assegnazione.

Sebbene il riferimento di un oggetto venga passato per valore, un metodo può comunque interagire con l'oggetto di riferimento chiamando i suoi metodi pubblici utilizzando la copia del riferimento dell'oggetto. Poiché il riferimento memorizzato nel parametro è una copia del riferimento passato come argomento, il parametro nel metodo chiamato e l'argomento nel metodo chiamante si riferiscono allo stesso oggetto in memoria.

Il passaggio di riferimenti ad array, anziché agli oggetti array stessi, ha senso per motivi di prestazioni. Poiché tutto in Java viene passato per valore, se vengono passati oggetti array, viene passata una copia di ciascun elemento. Per array di grandi dimensioni, ciò perderebbe tempo e richiederebbe un notevole spazio di archiviazione per le copie degli elementi.

Nell'immagine qui sotto puoi vedere che abbiamo due variabili di riferimento (Questi sono chiamati puntatori in C / C ++, e penso che quel termine renda più facile capire questa funzione.) Nel metodo principale. Le variabili primitive e di riferimento vengono mantenute nella memoria dello stack (lato sinistro nelle immagini seguenti). variabili di riferimento array1 e array2 "punto" (come lo chiamano i programmatori C / C ++) o riferimento rispettivamente alle matrici aeb, che sono oggetti (i valori che queste variabili di riferimento contengono sono indirizzi di oggetti) nella memoria dell'heap (lato destro nelle immagini sottostanti) .

Passa per valore esempio 1

Se passiamo il valore della variabile di riferimento array1 come argomento al metodo reverseArray, nel metodo viene creata una variabile di riferimento e tale variabile di riferimento inizia a puntare allo stesso array (a).

public class Test
{
    public static void reverseArray(int[] array1)
    {
        // ...
    }

    public static void main(String[] args)
    {
        int[] array1 = { 1, 10, -7 };
        int[] array2 = { 5, -190, 0 };

        reverseArray(array1);
    }
}

Passa per valore esempio 2

Quindi, se diciamo

array1[0] = 5;

nel metodo reverseArray, modificherà l'array a.

Abbiamo un'altra variabile di riferimento nel metodo reverseArray (array2) che punta a un array c. Se dovessimo dirlo

array1 = array2;

nel metodo reverseArray, quindi la variabile di riferimento array1 nel metodo reverseArray smetterebbe di puntare all'array a e inizierebbe a puntare all'array c (linea tratteggiata nella seconda immagine).

Se restituiamo il valore della variabile di riferimento array2 come valore di ritorno del metodo reverseArray e assegniamo questo valore al riferimento della variabile array1 nel metodo principale, l'array1 in main inizierà a puntare all'array c.

Quindi scriviamo tutte le cose che abbiamo fatto subito.

public class Test
{
    public static int[] reverseArray(int[] array1)
    {
        int[] array2 = { -7, 0, -1 };

        array1[0] = 5; // array a becomes 5, 10, -7

        array1 = array2; /* array1 of reverseArray starts
          pointing to c instead of a (not shown in image below) */
        return array2;
    }

    public static void main(String[] args)
    {
        int[] array1 = { 1, 10, -7 };
        int[] array2 = { 5, -190, 0 };

        array1 = reverseArray(array1); /* array1 of 
         main starts pointing to c instead of a */
    }
}

inserisci qui la descrizione dell'immagine

E ora che il metodo reverseArray è finito, le sue variabili di riferimento (array1 e array2) sono sparite. Ciò significa che ora abbiamo solo le due variabili di riferimento nel metodo principale array1 e array2 che indicano rispettivamente gli array ceb. Nessuna variabile di riferimento punta all'oggetto (array) a. Quindi è idoneo per la raccolta dei rifiuti.

È inoltre possibile assegnare il valore di array2 in main a array1. array1 inizierà a indicare b.


27

Ho creato un thread dedicato a questo tipo di domande per qualsiasi linguaggio di programmazione qui .

Viene anche menzionato Java . Ecco il breve riassunto:

  • Java gli passa i parametri per valore
  • "per valore" è l'unico modo in Java per passare un parametro a un metodo
  • l'utilizzo dei metodi dall'oggetto indicato come parametro modificherà l'oggetto poiché i riferimenti puntano agli oggetti originali. (se lo stesso metodo modifica alcuni valori)

27

Per farla breve, gli oggetti Java hanno alcune proprietà molto peculiari.

In generale, Java ha tipi primitivi ( int, bool, char, double, ecc) che sono passata direttamente per valore. Quindi Java ha oggetti (tutto ciò che deriva java.lang.Object). Gli oggetti in realtà vengono sempre gestiti attraverso un riferimento (un riferimento è un puntatore che non è possibile toccare). Ciò significa che in effetti gli oggetti vengono passati per riferimento, poiché i riferimenti normalmente non sono interessanti. Significa tuttavia che non è possibile modificare l'oggetto a cui punta il riferimento stesso viene passato per valore.

Sembra strano e confuso? Consideriamo come gli attrezzi C passano per riferimento e passano per valore. In C, la convenzione predefinita è pass per valore. void foo(int x)passa un int per valore. void foo(int *x)è una funzione che non vuole una int a, ma un puntatore ad un int: foo(&a). Uno userebbe questo con l' &operatore per passare un indirizzo variabile.

Porta questo in C ++ e abbiamo riferimenti. I riferimenti sono essenzialmente (in questo contesto) zucchero sintattico che nasconde la parte puntatore dell'equazione: void foo(int &x)viene chiamato da foo(a), dove il compilatore stesso sa che è un riferimento e l'indirizzo del non riferimento adovrebbe essere passato. In Java, tutte le variabili che fanno riferimento a oggetti sono in realtà di tipo di riferimento, forzando in effetti la chiamata per riferimento per la maggior parte delle intenzioni e degli scopi senza il controllo fine (e la complessità) fornito, ad esempio, dal C ++.


Penso che sia molto vicino alla mia comprensione dell'oggetto Java e del suo riferimento. L'oggetto in Java viene passato a un metodo da una copia di riferimento (o alias).
MaxZoom,
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.