Vraag Is Java "pass-by-reference" of "pass-by-value"?


Ik dacht altijd dat Java dat was pass-by-referentie.

Ik heb echter een aantal blogberichten gezien (bijvoorbeeld deze blog) die beweren dat dat niet zo is.

Ik denk niet dat ik het onderscheid begrijp dat ze maken.

Wat is de verklaring?


5482


oorsprong


antwoorden:


Java is altijd pass-by-value. Helaas besloten ze om de locatie van een object een "referentie" te noemen. Wanneer we de waarde van een object doorgeven, passeren we de referentie ernaar toe. Dit is verwarrend voor beginners.

Het gaat als volgt:

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
}

In het bovenstaande voorbeeld aDog.getName() zal nog steeds terugkeren "Max". De waarde aDog binnen main is niet veranderd in de functie foo met de Dog  "Fifi" als de objectreferentie per waarde wordt doorgegeven. Als het werd doorgegeven door verwijzing, dan is de aDog.getName() in main zou terugbrengen "Fifi" na de oproep aan foo.

Hetzelfde:

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

In het bovenstaande voorbeeld Fifi is de naam van de hond na oproep naar foo(aDog) omdat de naam van het object binnenin werd geplaatst foo(...). Alle bewerkingen die foo speelt op d zijn zodanig dat ze voor alle praktische doeleinden worden uitgevoerd aDog zelf (behalve wanneer d is veranderd om naar een ander te wijzen Dog bijvoorbeeld zoals d = new Dog("Boxer")).


4734



Ik heb net gemerkt dat je hebt verwezen mijn artikel.

De Java Spec geeft aan dat alles in Java pass-by-value is. Er bestaat niet zoiets als "pass-by-reference" in Java.

De sleutel tot het begrijpen hiervan is dat zoiets als

Dog myDog;

is niet een hond; het is eigenlijk een wijzer naar een hond.

Wat dat betekent, is wanneer je hebt

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

je bent in wezen de adres van de gecreëerde Dog bezwaar tegen de foo methode.

(Ik zeg in feite omdat Java-aanwijzers geen directe adressen zijn, maar het is het eenvoudigst om ze op die manier te bedenken)

Stel dat het Dog object staat op geheugenadres 42. Dit betekent dat we 42 doorgeven aan de methode.

als de methode is gedefinieerd als

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

laten we kijken naar wat er gebeurt.

  • de parameter someDog is ingesteld op de waarde 42
  • op regel "AAA"
    • someDog wordt gevolgd naar de Dog het wijst naar (de Dog object op adres 42)
    • dat Dog (die op adres 42) wordt gevraagd om zijn naam te veranderen in Max
  • op regel "BBB"
    • een nieuw Dog is gecreëerd. Laten we zeggen dat hij op adres 74 is
    • we kennen de parameter toe someDog tot 74
  • op regel "CCC"
    • someDog wordt gevolgd naar de Dog het wijst naar (de Dog object op adres 74)
    • dat Dog (die op adres 74) wordt gevraagd zijn naam in Rowlf te veranderen
  • dan keren we terug

Laten we nu eens nadenken over wat er buiten de methode gebeurt:

Deed myDog verandering?

Daar is de sleutel.

Rekening houdende met myDog is een wijzer, en niet een echte Dog, het antwoord is nee. myDog heeft nog steeds de waarde 42; het wijst nog steeds naar het origineel Dog(maar let op dat vanwege regel "AAA", de naam nu "Max" is - nog steeds dezelfde Hond; myDogDe waarde is niet veranderd.)

Het is perfect geldig voor volgen een adres en veranderen wat er aan het einde ervan is; dat verandert de variabele echter niet.

Java werkt precies zoals C. U kunt een aanwijzer toewijzen, de aanwijzer naar een methode leiden, de aanwijzer in de methode volgen en de gegevens wijzigen waarnaar is verwezen. U kunt echter niet wijzigen waar die aanwijzer wijst.

In C ++, Ada, Pascal en andere talen die pass-by-referentie ondersteunen, kunt u de variabele die werd gepasseerd feitelijk wijzigen.

Als Java een pass-by-reference semantiek had, de foo methode die we hierboven hebben gedefinieerd, zou waar zijn veranderd myDog was wijzend toen het werd toegewezen someDog op lijn BBB.

Zie referentieparameters als aliassen voor de doorgegeven variabele. Wanneer die alias is toegewezen, wordt de variabele die is doorgegeven, ook gebruikt.


2637



Java geeft altijd argumenten per waarde door, NIET door verwijzing.


Laat me dit uitleggen via een voorbeeld:

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

Ik zal dit in stappen uitleggen:

  1. Een verwijzing met de naam declareren f van type Foo en wijs het toe aan een nieuw object van type Foo met een attribuut "f".

    Foo f = new Foo("f");
    

    enter image description here

  2. Van de methode zijde, een verwijzing van het type Foo met een naam a wordt gedeclareerd en het is in eerste instantie toegewezen aan null.

    public static void changeReference(Foo a)
    

    enter image description here

  3. Zoals je de methode noemt changeReference, de referentie a wordt toegewezen aan het object dat als een argument wordt doorgegeven.

    changeReference(f);
    

    enter image description here

  4. Een verwijzing met de naam declareren b van type Foo en wijs het toe aan een nieuw object van type Foo met een attribuut "b".

    Foo b = new Foo("b");
    

    enter image description here

  5. a = b is de referentie opnieuw aan het toewijzen a NIET f naar het object waarvan het attribuut is "b".

    enter image description here


  6. Terwijl je belt modifyReference(Foo c) methode, een referentie c wordt gemaakt en toegewezen aan het object met attribuut "f".

    enter image description here

  7. c.setAttribute("c"); zal het attribuut van het object veranderen waarnaar verwezen wordt c wijst erop, en het is hetzelfde object dat verwijst f wijst erop.

    enter image description here

Ik hoop dat je nu begrijpt hoe passerende objecten als argumenten in Java werken :)


1388



Dit geeft je een aantal inzichten over hoe Java echt werkt tot het punt dat je in je volgende discussie over Java die voorbijgaat door verwijzing of waarde voorbijgaat, gewoon glimlacht :-)

Stap één wis alsjeblieft dat woord dat begint met 'p' "_ _ _ _ _ _ _" uit, vooral als je uit andere programmeertalen komt. Java en 'p' kunnen niet in hetzelfde boek, forum of zelfs txt worden geschreven.

Stap twee onthoud dat wanneer u een object in een methode slaagt, u de objectreferentie passeert en niet het object zelf.

  • Student: Meester, betekent dit dat Java pass-by-referentie is?
  • Meester: Grasshopper, nee

Bedenk nu wat de referentie / variabele van een object is / is:

  1. Een variabele bevat de bits die de JVM vertellen hoe het object waarnaar wordt verwezen in het geheugen (Heap) te krijgen.
  2. Bij het doorgeven van argumenten aan een methode je GAAT NIET de referentievariabele door, maar een kopie van de bits in de referentievariabele. Iets als dit: 3bad086a. 3bad086a staat voor een manier om bij het doorgegeven object te komen.
  3. U geeft dus gewoon 3bad086a door dat het de waarde van de referentie is.
  4. U geeft de waarde van de referentie door en niet de referentie zelf (en niet het object).
  5. Deze waarde wordt feitelijk gekopieerd en aan de methode gegeven.

Voer het volgende uit (probeer dit niet te compileren / uitvoeren ...):

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

Wat gebeurt er?

  • De variabele persoon is gemaakt in regel # 1 en is nul aan het begin.
  • Er wordt een nieuw persoonsobject gemaakt in regel # 2, opgeslagen in het geheugen en de variabele persoon krijgt de verwijzing naar het person-object. Dat wil zeggen, het adres. Laten we zeggen 3bad086a.
  • De variabele persoon vasthouden van het adres van het object wordt doorgegeven aan de functie in regel # 3.
  • In regel # 4 kun je naar het geluid van stilte luisteren
  • Controleer de opmerking op regel # 5
  • Een methode lokale variabele -anotherReferenceToTheSamePersonObject- is gemaakt en komt dan de magie in lijn # 6:
    • De variabele / referentie persoon wordt bit voor bit gekopieerd en doorgegeven aan anotherReferenceToTheSamePersonObject binnen de functie.
    • Er worden geen nieuwe exemplaren van de persoon gemaakt.
    • Beide "persoon"en"anotherReferenceToTheSamePersonObject"behoudt dezelfde waarde als 3bad086a.
    • Probeer dit niet, maar person == anotherReferenceToTheSamePersonObject zou waar zijn.
    • Beide variabelen hebben IDENTIEKE kopieën van de referentie en ze verwijzen allebei naar hetzelfde persoonsobject, hetZELFDE object op de heap en GEEN KOPIE.

Een foto zegt meer dan duizend woorden:

Pass by Value

Merk op dat de pijlen anotherReferenceToTheSamePersonObject op het object zijn gericht en niet op de variabele persoon!

Als je het niet hebt gekregen, vertrouw me dan gewoon en onthoud dat het beter is dat te zeggen Java is waardevol. Goed, doorgeven door referentiewaarde. Nou ja, nog beter is pass-by-copy-of-the-variabele-waarde! ;)

Nu voel je vrij om mij te haten, maar merk op dat dit gegeven er is geen verschil tussen het doorgeven van primitieve gegevenstypen en objecten wanneer we het hebben over methodeargumenten.

Je geeft altijd een kopie van de stukjes van de waarde van de referentie!

  • Als het een primitief gegevenstype is, bevatten deze bits de waarde van het primitieve gegevenstype zelf.
  • Als het een object is, bevatten de bits de waarde van het adres dat de JVM vertelt hoe het object te bereiken.

Java is pass-by-value, omdat je binnen een methode het Object waarnaar wordt verwezen kunt aanpassen zoals je wilt, maar hoe hard je het ook probeert, je zult nooit de doorgegeven variabele die zal blijven refereren (niet p _ _ _ _ _ _ _) hetzelfde object maakt niet uit wat!


De changeName-functie hierboven zal nooit in staat zijn om de feitelijke inhoud (de bitwaarden) van de doorgegeven referentie te wijzigen. Met andere woorden changeName kan Persoon niet naar een ander Object verwijzen.


Natuurlijk kun je het kort houden en dat gewoon zeggen Java is pass-by-value!


651



Java is altijd waardevol, zonder uitzonderingen, ooit.

Dus hoe komt het dat iemand hierdoor überhaupt in de war kan worden gebracht, en geloven dat Java een referentie is, of denken dat ze een voorbeeld van Java hebben dat optreedt als referentie? Het belangrijkste punt is dat Java nooit biedt directe toegang tot de waarden van objecten zelf, in ieder situatie. De enige toegang tot objecten is via een referentie naar dat object. Omdat Java-objecten zijn altijd toegankelijk via een referentie, in plaats van rechtstreeks, is het gebruikelijk om te praten over velden en variabelen en methode argumenten als wezen voorwerpen, wanneer ze pedant genoeg alleen zijn verwijzingen naar objecten. De verwarring komt voort uit deze (strikt gesproken, onjuiste) verandering in nomenclatuur.

Dus bij het aanroepen van een methode

  • Voor primitieve argumenten (int, long, etc.), is de pass-by-waarde de werkelijke waarde van de primitieve (bijvoorbeeld 3).
  • Voor objecten is de pass-by-waarde de waarde van de verwijzing naar het object.

Dus als je dat hebt gedaan doSomething(foo) en public void doSomething(Foo foo) { .. } de twee Foos hebben gekopieerd referenties dat verwijst naar dezelfde objecten.

Uiteraard lijkt een verwijzing naar een object heel erg op (en is in de praktijk niet te onderscheiden van) het doorgeven van een object door middel van verwijzing.


561



Java geeft verwijzingen door waarde door.

U kunt dus de referentie die wordt doorgegeven niet wijzigen.


278



Ik heb er moeite mee te discussiëren over "pass-by-reference vs pass-by-value" is niet super-behulpzaam.

Als u zegt: "Java is pass-by-whatever (referentie / waarde)", in beide gevallen geeft u geen volledig antwoord. Hier is wat extra informatie die hopelijk zal helpen bij het begrijpen wat er in het geheugen gebeurt.

Crashcursus op stapel / heap voordat we bij de Java-implementatie komen: Waarden gaan op en af ​​van de stapel op een mooie ordelijke manier, zoals een stapel platen in een cafetaria. Geheugen in de heap (ook bekend als dynamisch geheugen) is lukraak en ongeorganiseerd. De JVM vindt alleen ruimte waar het kan, en maakt het vrij omdat de variabelen die het gebruiken niet langer nodig zijn.

Oke. Allereerst gaan lokale primitieven op de stapel. Dus deze code:

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

resultaten in dit:

primitives on the stack

Wanneer u een object declareert en instantieert. Het eigenlijke object gaat op de hoop. Wat gebeurt er op de stapel? Het adres van het object op de hoop. C ++ programmeurs noemen dit een pointer, maar sommige Java-ontwikkelaars zijn tegen het woord "pointer". Wat dan ook. Weet gewoon dat het adres van het object op de stapel staat.

Zoals zo:

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

a b*7ch aint one!

Een array is een object, dus het gaat ook op de heap. En hoe zit het met de objecten in de array? Ze krijgen hun eigen heap-ruimte en het adres van elk object komt in de array terecht.

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

marx brothers

Dus wat wordt er doorgegeven als je een methode aanroept? Als u een object doorgeeft, is wat u in werkelijkheid doorgeeft het adres van het object. Sommigen zouden de "waarde" van het adres kunnen zeggen, en sommigen zeggen dat het slechts een verwijzing naar het object is. Dit is het ontstaan ​​van de heilige oorlog tussen voorstanders van 'referentie' en 'waarde'. Hoe je het noemt, is niet zo belangrijk als dat je begrijpt dat wat wordt doorgegeven het adres van het object is.

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

One String wordt aangemaakt en er wordt ruimte voor toegewezen in de heap, en het adres van de string wordt opgeslagen op de stack en krijgt de identifier hisName, omdat het adres van de tweede tekenreeks hetzelfde is als de eerste, wordt er geen nieuwe tekenreeks gemaakt en wordt geen nieuwe heap-ruimte toegewezen, maar wordt er een nieuwe ID op de stapel gemaakt. Dan bellen we shout(): een nieuw stackframe wordt gemaakt en een nieuwe identifier, name wordt gemaakt en toegewezen aan het adres van de reeds bestaande string.

la da di da da da da

Dus, waarde, referentie? U zegt "aardappel".


198



Vergelijk het volgende om het contrast te laten zien C ++ en Java snippets:

In C ++: Opmerking: Slechte code - geheugenlekken!  Maar het toont het punt aan.

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 heeft alleen de twee soorten doorgeven: op waarde voor ingebouwde typen en op waarde van de aanwijzer voor objecttypen.


161



Java geeft verwijzingen naar objecten door waarde door.


145