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




array parameter (25)

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?


Answers

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.


Solo per mostrare il contrasto, confronta i seguenti frammenti 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 i due tipi di passaggio: per valore per i tipi predefiniti e per valore del puntatore per i tipi di oggetto.


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.


Java ha passato solo 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();
}

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

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


Lo penso sempre come "passa per copia". È una copia del valore sia primitiva che di riferimento. Se è un primitivo è 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;
    }
}

output di java PassByCopy:

nome = nome Maxx
= Fido

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


Fondamentalmente, la riassegnazione dei parametri dell'oggetto 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, allora foo avrebbe ridefinito il baz in null .


La distinzione, o forse solo il modo in cui mi ricordo come avevo una volta 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 sono passati per valore.


Per quanto ne so, Java sa solo chiamare per valore. Questo 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; per 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 usano copys che non hanno alcun impatto sui riferimenti nel main. 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 sulla riga di comando. Se cambi StringBuffer in String, 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);
}

Comunque potresti creare un wrapper per String come questo che lo renderebbe in grado di usarlo con le 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);
}

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


Java passa i parametri per VALUE e SOLO per valore .

Per farla breve:

Per quelli provenienti da C #: NON C'È alcun parametro "out".

Per chi proviene da PASCAL: NON C'È un parametro "var" .

Significa che non puoi cambiare il riferimento dall'oggetto stesso, ma puoi sempre modificare le proprietà dell'oggetto.

Una soluzione alternativa è utilizzare il StringBuilderparametro String. E puoi sempre usare gli array!


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


Ho appena notato che hai fatto riferimento al mio articolo .

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

La chiave per capire questo è qualcosa di simile

Dog myDog;

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

Cosa significa, è quando hai

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

stai essenzialmente passando l' indirizzo dell'oggetto Dog creato al metodo foo .

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

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

se il metodo è stato definito come

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

diamo un'occhiata a cosa sta succedendo.

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

Ora pensiamo a cosa succede al di fuori del metodo:

myDog cambiato?

C'è la chiave.

Tenendo presente che myDog è un puntatore e non un vero Dog , la risposta è NO. myDog ha ancora il valore 42; sta ancora puntando al Dog originale (ma nota che a causa della linea "AAA", il suo nome ora è "Max" - è ancora lo stesso Cane; il valore di myDog non è cambiato.)

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

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

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

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

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


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 è 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") ).


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

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

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

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

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

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

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

1. Person person;
2. person = new Person("Tom");
3. changeName(person);
4.
5. //I didn't use Person person below as an argument to be nice
6. static void changeName(Person anotherReferenceToTheSamePersonObject) {
7.     anotherReferenceToTheSamePersonObject.setName("Jerry");
8. }

Che succede?

  • La persona variabile viene creata nella riga n. 1 ed è nulla all'inizio.
  • Un nuovo Oggetto Persona viene creato nella riga n. 2, memorizzato nella memoria e alla persona variabile viene assegnato 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.
  • Alla riga 4 puoi ascoltare il suono del silenzio
  • Controlla il commento sulla linea # 5
  • Una variabile locale del metodo - anotherReferenceToTheSamePersonObject - viene creata e poi viene la magia nella riga n. 6:
    • La variabile / persona di riferimento viene copiata bit per bit e passata a anotherReferenceToTheSamePersonObject all'interno della funzione.
    • Non vengono create nuove istanze di Person.
    • Sia " persona " che " anotherReferenceToTheSamePersonObject " mantengono lo stesso valore di 3bad086a.
    • Non provare questo, ma person == anotherReferenceToTheSamePersonObject sarebbe vero.
    • Entrambe le variabili hanno COPIE IDENTICHE del riferimento e si riferiscono entrambi allo stesso oggetto Person, allo SAME Object sull'Heap e NON A COPY.

Un'immagine vale più di mille parole:

Nota che le frecce anotherReferenceToTheSamePersonObject 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 . Oh bene, ancora meglio è il valore della copia pass-by-the-variable! ;)

Ora sentitevi liberi di odiarmi, ma osservate che, dato ciò, non vi è alcuna differenza tra il passaggio di tipi di dati primitivi e Oggetti quando si parla di argomenti del metodo.

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

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

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

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

Ovviamente puoi tagliarlo brevemente e dire semplicemente che Java è un valore pass-by!


Lasciatemi provare a spiegare la mia comprensione con l'aiuto di quattro esempi. Java è un valore pass-by e non un riferimento pass-by

/ **

Passa per valore

In Java, tutti i parametri vengono passati per valore, cioè l'assegnazione di un argomento 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:

/ ** * * Passaggio 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 'Passaggio per valore ha la sensazione di' Passa per riferimento '

Alcuni dicono tipi primitivi e "String" sono "passano per valore" e gli oggetti sono "passano per riferimento".

Ma da questo esempio, possiamo capire che è effettivamente passato per valore, tenendo presente che qui stiamo passando il riferimento come valore. cioè: il riferimento è passato per valore. Questo è il motivo per cui sono in grado di cambiare e rimane vero dopo l'ambito locale. Ma non possiamo cambiare il riferimento reale al di fuori dell'ambito originale. ciò che significa è 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 nell'Esempio 3 (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è uguale a Example3.

* /

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]

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


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 .


Java è una chiamata in base al valore.

Come funziona

  • Si passa 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, allora non rifletterà le modifiche all'esterno.

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


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


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)

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

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

I dati in memoria hanno una Location e in quella posizione c'è un valore (byte, word, qualunque). In Assembly abbiamo una comoda soluzione per assegnare un Nome a una determinata posizione (variabile aka), ma quando si compila il codice, l'assemblatore semplicemente sostituisce Nome con la posizione designata, proprio come il browser sostituisce i nomi di dominio con gli indirizzi IP.

Giù fino al midollo è tecnicamente impossibile passare un riferimento a qualcosa 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 è a 223rd byte in memoria, e il suo valore sarà 47. Questo Ref2Foo potrebbe essere una variabile tecnica , non creato esplicitamente dal programma. Se guardi solo 5 e 47 senza altre informazioni, vedrai solo due valori . Se li usi come riferimenti, allora per raggiungere 5dobbiamo viaggiare:

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

Ecco come funzionano i jump-tables.

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 chiamata del metodo :

  1. 5 viene copiata in uno dei registri della CPU (ad esempio EAX).
  2. 5 ottiene PUSHd in pila.
  3. 47 viene copiata in uno dei registri della CPU
  4. 47 PUSH in pila.
  5. 223 viene copiata in uno dei registri della CPU.
  6. 223 ottiene PUSHd in pila.

In tutti i casi sopra i quali è stato creato un valore, una copia di un valore esistente, è ora disponibile il metodo di ricezione per gestirlo. Quando scrivi "Foo" all'interno del metodo, viene letto da EAX, o automaticamente dereferenziato , o dereferenziato, il processo dipende da come funziona la lingua e / o da quale tipo di Foo detta. Questo è nascosto allo sviluppatore fino a che 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 modifica Foo ( Foo = 9), questo ha effetto solo sull'ambito locale in quanto si ha una copia del Valore. Dall'interno del metodo non siamo nemmeno in grado di determinare in quale posizione si trovasse la Foo originale.
  • nel caso 3. e 4. se si usano costrutti di linguaggio predefiniti e si cambia Foo ( Foo = 11), potrebbe cambiare Foo globalmente (dipende dal linguaggio, ad esempio Java o come Pascal's procedure findMin(x, y, z: integer; var m : integer); ). Tuttavia, se il linguaggio consente di aggirare il processo di dereferenziamento, puoi cambiare 47, dillo a 49. A quel punto Foo sembra essere stato cambiato se lo hai letto, perché hai cambiato il puntatore locale ad esso. E se dovessi modificare questo Foo all'interno del metodo ( Foo = 12) probabilmente FUBAR eseguirai il programma (noto come segfault) perché scriverai su una memoria diversa da quella prevista, puoi anche modificare un'area che è destinata a contenere un eseguibile programma e la scrittura in esso modificherà il codice in esecuzione (Foo non è ora a 47). Ma il valore di Foo di47non è cambiato a livello globale, solo quello all'interno del metodo, perché 47era anche una copia del metodo.
  • nel caso 5. e 6. se si modifica 223all'interno del metodo si crea lo stesso caos come in 3. o 4. (un puntatore, che punta a un valore ora cattivo, che viene nuovamente utilizzato come puntatore) ma questo è ancora un locale problema, come 223 è stato copiato . Tuttavia se sei in grado di dereferenziare Ref2Foo(cioè 223), raggiungere e modificare il valore puntato 47, per esempio, 49influirà su Foo a livello globale , perché in questo caso i metodi hanno una copia di 223ma il riferimento 47esiste solo una volta e cambiando quello al 49porterà ogni Ref2Foodoppio dereferenziazione ad un valore errato.

Facendo clic su dettagli insignificanti, anche i linguaggi che passano per riferimento passano i valori alle funzioni, ma tali funzioni sanno che devono usarli per scopi di dereferenziazione. Questo pass-the-reference-as-value è appena nascosto dal programmatore perché è praticamente inutile e la terminologia è solo pass-by-reference .

Anche il pass-by-value rigoroso è 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 strettamente pass-by-value. Ogni linguaggio passerebbe un riferimento a questo enorme array (come valore) e utilizza il meccanismo copy-on-write se tale array può essere modificato localmente all'interno del metodo o consente al metodo (come Java) di modificare l'array globalmente (da vista del chiamante) e alcune lingue consente di modificare il valore del riferimento stesso.

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


Java passa i riferimenti agli oggetti in base al valore.


Alcune correzioni ad alcuni post.

C NON supporta il passaggio per riferimento. È SEMPRE passare di valore. C ++ supporta pass per riferimento, ma non è il valore predefinito ed è piuttosto pericoloso.

Non importa quale sia il valore in Java: primitivo o indirizzo (grosso modo) dell'oggetto, esso viene SEMPRE passato per valore.

Se un oggetto Java "si comporta" come se fosse passato per riferimento, questa è una proprietà di mutabilità e non ha assolutamente nulla a che fare con i meccanismi di passaggio.

Non sono sicuro del perché questo sia così confuso, forse perché così tanti "programmatori" Java non sono formalmente addestrati e quindi non capiscono cosa sta realmente accadendo in memoria?


java.lang.Enum definisce diversi metodi utili, disponibili per tutti i tipi di enumerazione in Java:

  • È possibile utilizzare il metodo name() per ottenere il nome di tutte le costanti Enum. Il letterale stringa usato per scrivere le costanti enum è il loro nome.
  • Allo stesso modo il metodo values() può essere usato per ottenere una matrice di tutte le costanti Enum da un tipo Enum.
  • E per la domanda posta, è possibile utilizzare il metodo valueOf() per convertire qualsiasi costante String in Enum in Java, come mostrato di seguito.
public class EnumDemo06 {
    public static void main(String args[]) {
        Gender fromString = Gender.valueOf("MALE");
        System.out.println("Gender.MALE.name() : " + fromString.name());
    }

    private enum Gender {
        MALE, FEMALE;
    }
}

Output:
Gender.MALE.name() : MALE

In questo snippet di codice, il metodo valueOf() restituisce una costante Enum Gender.MALE, chiamando il nome che restituisce "MALE" .





java methods parameter-passing pass-by-reference pass-by-value