java or array - Java è "pass-by-reference" o "pass-by-value"?




15 Answers

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() restituirà ancora "Max" . Il valore aDog all'interno di main non viene modificato nella funzione foo con Dog "Fifi" poiché il riferimento all'oggetto viene passato per valore. Se fosse passato per riferimento, allora aDog.getName() in main restituirebbe "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(...) . Tutte le operazioni che foo esegue su d sono tali che, per tutti gli scopi pratici, vengono eseguite su aDog stesso (tranne quando d viene modificato in modo da puntare a un'istanza Dog diversa come d = new Dog("Boxer") ).

parameter copy

Ho sempre pensato che Java fosse un riferimento pass-by .

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

Non credo di capire la distinzione che stanno facendo.

Qual è la spiegazione?




Java passa sempre argomenti per valore NON per riferimento.

Lasciatemi spiegare questo attraverso un example :

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. Dichiarare 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");
    

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

    public static void changeReference(Foo a)
    

  3. Quando chiami il metodo changeReference , il riferimento a verrà assegnato all'oggetto che viene passato come argomento.

    changeReference(f);
    

  4. Dichiarare 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");
    

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

  6. Quando chiamate il modifyReference(Foo c) , viene creato un riferimento c viene assegnato all'oggetto con l'attributo "f" .

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

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




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 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 attraverso 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 gli argomenti primitivi ( int , long , ecc.), Il valore passa per valore è il valore reale della primitiva (ad esempio, 3).
  • Per gli oggetti, il passaggio per valore è il valore del riferimento all'oggetto .

Quindi se 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.




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 on 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:

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

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

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'identificativo 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, il name viene creato e assegnato l'indirizzo della String già esistente.

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




Java passa i riferimenti agli oggetti in base al valore.




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




In java tutto è di riferimento, quindi quando si ha qualcosa di simile: Point pnt1 = new Point(0,0);Java segue:

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

Java non passa gli argomenti del metodo per riferimento; li passa per valore. Userò un 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 oggetti Point diversi con due diversi riferimenti associati.

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

Come previsto, il risultato sarà:

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

Su questa linea 'pass-by-value' entra nel gioco ...

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

Riferimenti pnt1e pnt2sono passati di valore al metodo difficile, il che significa che ora i tuoi riferimenti pnt1e pnt2hanno il loro copiesnome arg1e arg2.So pnt1e arg1 punta allo stesso oggetto. (Lo stesso vale per pnt2e arg2)

Nel trickymetodo:

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

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 muovi il riferimento arg1per puntare allo stesso posto come arg2riferimento. Infine arg2sarà puntare allo stesso posto come temp.

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, al tuo ritorno main, hai questa situazione:

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

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



Java passa sempre per valore, non passa per riferimento

Prima di tutto, dobbiamo capire cosa valgono per valore e passano per riferimento.

Passare per valore significa che stai facendo una copia in memoria del valore del parametro attuale che è passato. Questa è una copia del contenuto del parametro attuale .

Passa per riferimento (detto anche indirizzo per indirizzo) significa che è stata memorizzata una copia dell'indirizzo del parametro attuale .

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

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

Capiamo 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 il valore interno effettivo di JVM, questo è solo un esempio).

new PassByValue().changeValue(t);

Quando si passa il riferimento t alla funzione, non passerà direttamente il valore di riferimento effettivo del test dell'oggetto, ma creerà una copia di t e quindi lo passerà alla funzione. Poiché passa per valore , passa una copia della variabile piuttosto che 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 punteranno allo stesso oggetto.

Se cambi qualcosa nella funzione usando il riferimento f, modificherà il contenuto esistente dell'oggetto. Questo è il motivo per cui abbiamo ottenuto l'output changevalue, che viene aggiornato nella funzione.

Per capire questo 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 lo farà NullPointerException? No, perché passa solo una copia del riferimento. Nel caso di passaggio per riferimento, potrebbe essere stato lanciato un NullPointerException, come mostrato di seguito:

Speriamo che questo possa aiutare.




No, non è passato per riferimento.

Java è passato per valore in base alla specifica 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 , ciascuno del tipo dichiarato, prima dell'esecuzione del corpo del metodo o del costruttore. L'identificatore che appare nel DeclaratorId può essere usato come un nome semplice nel corpo del metodo o costruttore per fare riferimento al parametro formale .




Non si può mai passare per riferimento in Java e uno dei modi ovvi è quando si desidera restituire più di un valore da una chiamata di 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 pattern in Java, ma non puoi; almeno non direttamente. Invece si potrebbe fare qualcosa di simile:

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 è stato spiegato nelle risposte precedenti, in Java stai passando un puntatore all'array come valore in getValues. Questo è sufficiente, perché il metodo quindi modifica l'elemento dell'array e, per convenzione, ti aspetti che l'elemento 0 contenga il valore restituito. Ovviamente puoi farlo in altri modi, come la strutturazione del tuo codice, quindi questo non è necessario o la costruzione di una classe che può contenere il valore restituito o consentirne l'impostazione. Ma il semplice schema disponibile in C ++ sopra non è disponibile in Java.




Ho pensato di contribuire a questa risposta per aggiungere ulteriori dettagli dalle specifiche.

Innanzitutto, qual è la differenza tra il passaggio per riferimento e il 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, sul tema del pass-by-reference

Nella valutazione call-by-reference (detta anche pass-by-reference), una funzione riceve un riferimento implicito a una variabile usata come argomento, piuttosto che una copia del suo valore. Questo in genere significa che la funzione può modificare (cioè assegnare a) la variabile utilizzata come argomento, qualcosa che verrà visualizzato dal chiamante.

E sul tema del pass-by-value

In call-by-value, l'espressione argomento viene valutata 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 [...].

Secondo, abbiamo bisogno di sapere cosa Java usa nelle sue invocazioni di metodo. Gli stati della specifica 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 , ciascuno del tipo dichiarato, prima dell'esecuzione del corpo del metodo o del costruttore.

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

Qual è il valore dell'argomento?

Consideriamo i tipi di riferimento, gli stati delle specifiche di Java Virtual Machine

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

Si afferma anche la specifica del linguaggio Java

I valori di riferimento (spesso solo riferimenti) sono puntatori a questi oggetti e uno speciale riferimento null, che 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 di ritorno del 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 associare 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. In quanto tale, Java è pass-by-value .

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

Questo in genere significa che la funzione può modificare (cioè assegnare a) la variabile utilizzata come argomento, qualcosa che verrà visualizzato dal chiamante.

In Java, modificare la variabile significa riassegnarlo. In Java, se si riassegnasse 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 nella specifica Java Virtual Machine, here . Il valore del tipo è il corrispondente valore in virgola mobile o integrale, codificato appropriatamente (8, 16, 32, 64, ecc. Bit).




Come molte persone hanno menzionato prima, Java è sempre pass-by-value

Ecco un altro esempio che ti aiuterà a capire la differenza ( l'esempio di swap classico ):

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 (puntano rispettivamente a aeb). Quindi, provare a cambiare i riferimenti di iA o iB cambierà solo nell'ambito locale e non al di fuori di questo metodo.




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

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

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

In caso di oggetti, questo è lo stesso: le variabili oggetto sono puntatori (bucket) che contengono solo l' indirizzo dell'oggetto che è stato creato utilizzando la parola chiave "new" 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), il contenuto / i membri dell'oggetto potrebbero ancora essere modificati all'interno di un metodo e successivamente l'accesso all'esterno, dando l'illusione che l'oggetto (contenente) è stato passato per riferimento.

Gli oggetti "a stringa" sembrano essere un perfetto contro-esempio alla leggenda metropolitana dicendo che "Gli oggetti vengono passati per riferimento":

In effetti, all'interno di un metodo che 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 finale che non può essere modificato. Solo l'indirizzo dell'oggetto può essere sostituito da un altro utilizzando "nuovo". L'utilizzo di "new" per aggiornare la variabile, non consentirà l'accesso all'oggetto dall'esterno, poiché la variabile è stata inizialmente passata per valore e copiata.




Ho creato una discussione dedicata a questo tipo di domande per tutti i linguaggi di programmazione here .

Viene anche menzionato Java . Ecco il breve riassunto:

  • Java passa i parametri in base al valore
  • "by value" è l'unico modo in java per passare un parametro ad un metodo
  • l'utilizzo di metodi dall'oggetto fornito come parametro modificherà l'oggetto come i riferimenti puntano agli oggetti originali. (se quel metodo stesso altera alcuni valori)



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

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

Questo suona strano e confuso? Consideriamo come le implementazioni C passano per riferimento e passano per valore. In C, la convenzione di default è passata per valore. void foo(int x)passa un int in base al 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.

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




Related