Frage Ist Java "pass-by-reference" oder "pass-by-value"?


Ich dachte immer, dass Java war Pass-by-Reference.

Ich habe jedoch ein paar Blogposts gesehen (zum Beispiel dieser Blog) das behaupten, dass es nicht ist.

Ich glaube nicht, dass ich den Unterschied verstehe, den sie machen.

Was ist die Erklärung?


5482


Ursprung


Antworten:


Java ist immer pass-by-value. Leider entschieden sie sich, den Standort eines Objekts als "Referenz" zu bezeichnen. Wenn wir den Wert eines Objekts übergeben, passieren wir die Referenz zu ihm. Dies ist für Anfänger verwirrend.

Es geht so:

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
}

Im obigen Beispiel aDog.getName() wird immer noch zurückkehren "Max". Der Wert aDog innerhalb main wird in der Funktion nicht geändert foo mit dem Dog  "Fifi" da die Objektreferenz nach Wert übergeben wird. Wenn es durch Bezugnahme bestanden wurde, dann die aDog.getName() im main würde zurückkehren "Fifi" nach dem Anruf nach foo.

Gleichfalls:

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

Im obigen Beispiel Fifi ist der Name des Hundes nach Anruf zu foo(aDog) weil der Name des Objekts innerhalb von gesetzt wurde foo(...). Alle Operationen, die foo führt auf d sind so, dass sie für alle praktischen Zwecke ausgeführt werden aDog selbst (außer wenn d wird geändert, um auf ein anderes zu zeigen Dog Instanz wie d = new Dog("Boxer")).


4734



Ich habe gerade bemerkt, dass du referenziert hast mein Artikel.

Die Java-Spezifikation besagt, dass alles in Java pass-by-value ist. In Java gibt es kein "pass-by-reference".

Der Schlüssel zum Verständnis ist, dass so etwas wie

Dog myDog;

ist nicht ein Hund; es ist eigentlich ein Zeiger zu einem Hund.

Was das bedeutet, ist wann du hast

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

Du gehst im Wesentlichen vorbei Adresse von dem Erschaffenen Dog Objekt an die foo Methode.

(Ich sage im Wesentlichen, weil Java-Zeiger keine direkten Adressen sind, aber es ist am einfachsten, sie so zu denken)

Angenommen, die Dog Das Objekt befindet sich an der Speicheradresse 42. Dies bedeutet, dass wir 42 an die Methode übergeben.

wenn die Methode als definiert wäre

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

Schauen wir uns an, was passiert.

  • der Parameter someDog ist auf den Wert 42 eingestellt
  • bei der Linie "AAA"
    • someDog ist bis zum folgen Dog es zeigt auf (die Dog Objekt an Adresse 42)
    • Das Dog (der an Adresse 42) wird gebeten, seinen Namen in Max zu ändern
  • bei der Linie "BBB"
    • ein neuer Dog geschaffen. Sagen wir, er ist an Adresse 74
    • Wir weisen den Parameter zu someDog bis 74
  • in der Zeile "CCC"
    • someDog wird zum Dog es zeigt auf (die Dog Objekt an Adresse 74)
    • Das Dog (der an Adresse 74) wird gebeten, seinen Namen in Rowlf zu ändern
  • dann kehren wir zurück

Lasst uns nun darüber nachdenken, was außerhalb der Methode passiert:

Hat myDog Veränderung?

Da ist der Schlüssel.

Das im Hinterkopf behalten myDog ist ein Zeigerund nicht ein tatsächliches Dog, Die Antwort ist nein. myDog hat immer noch den Wert 42; es zeigt immer noch auf das Original Dog(aber wegen der Linie "AAA" ist sein Name jetzt "Max" - immer noch derselbe Hund; myDogDer Wert hat sich nicht geändert.)

Es ist absolut gültig für Folgen eine Adresse und ändern, was am Ende ist; das ändert die Variable jedoch nicht.

Java funktioniert genau wie C. Sie können einen Zeiger zuweisen, den Zeiger einer Methode übergeben, dem Zeiger in der Methode folgen und die Daten ändern, auf die verwiesen wurde. Sie können jedoch nicht ändern, wo dieser Zeiger zeigt.

In C ++, Ada, Pascal und anderen Sprachen, die Pass-by-Reference unterstützen, können Sie die Variable ändern, die übergeben wurde.

Wenn Java eine Semantik für die Weitergabe durch Verweis hätte, würde die foo Methode, die wir oben definiert hätten, hätte sich wo geändert myDog zeigte auf, als es zugewiesen wurde someDog auf der Linie BBB.

Stellen Sie sich Referenzparameter als Aliase für die übergebene Variable vor. Wenn dieser Alias ​​zugewiesen wird, ist dies auch die Variable, die übergeben wurde.


2637



Java übergibt Argumente stets nach Wert, NICHT per Verweis.


Lass mich das durch ein erklären Beispiel:

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

Ich werde das in Schritten erklären:

  1. Eine Referenz benennen f vom Typ Foo und weisen Sie es einem neuen Objekt vom Typ zu Foo mit einem Attribut "f".

    Foo f = new Foo("f");
    

    enter image description here

  2. Von der Methodenseite eine Referenz vom Typ Foo mit einem Namen a wird deklariert und es ist anfänglich zugewiesen null.

    public static void changeReference(Foo a)
    

    enter image description here

  3. Wie Sie die Methode aufrufen changeReference, die Referenz a wird dem Objekt zugewiesen, das als Argument übergeben wird.

    changeReference(f);
    

    enter image description here

  4. Eine Referenz benennen b vom Typ Foo und weisen Sie es einem neuen Objekt vom Typ zu Foo mit einem Attribut "b".

    Foo b = new Foo("b");
    

    enter image description here

  5. a = b wird die Referenz neu zugewiesen a NICHT f zu dem Objekt, dessen Attribut es ist "b".

    enter image description here


  6. Wie du rufst modifyReference(Foo c) Methode, eine Referenz c wird erstellt und dem Objekt mit Attribut zugewiesen "f".

    enter image description here

  7. c.setAttribute("c"); ändert das Attribut des Objekts, auf das verwiesen wird c weist darauf hin, und es ist das gleiche Objekt, das Referenz f weist darauf hin.

    enter image description here

Ich hoffe, Sie verstehen jetzt, wie Objekte als Argumente in Java übergeben werden :)


1388



Dies wird Ihnen einige Einblicke geben, wie Java wirklich so funktioniert, dass Sie in Ihrer nächsten Diskussion über Java, das durch Referenz oder Wertübergehen geht, nur lächeln werden :-)

Schritt eins bitte lösche aus deinem Kopf das Wort, das mit 'p' "_ _ _ _ _ _ _" beginnt, besonders wenn du aus anderen Programmiersprachen kommst. Java und 'p' können nicht im selben Buch, Forum oder sogar txt geschrieben werden.

Schritt zwei erinnert daran, dass Sie beim Übergeben eines Objekts an eine Methode die Objektreferenz und nicht das Objekt selbst übergeben.

  • Schüler: Meister, bedeutet das, dass Java Pass-by-Reference ist?
  • Meister: Grashüpfer, Nein.

Denken Sie nun darüber nach, was die Referenz / Variable eines Objekts ist / ist:

  1. Eine Variable enthält die Bits, die der JVM mitteilen, wie sie zu dem referenzierten Objekt im Speicher (Heap) gelangen.
  2. Wenn Argumente an eine Methode übergeben werden Sie übergeben NICHT die Referenzvariable, sondern eine Kopie der Bits in der Referenzvariablen. Etwas wie das: 3bad086a. 3bad086a stellt eine Möglichkeit dar, zum übergebenen Objekt zu gelangen.
  3. Sie übergeben 3bad086a nur, dass es sich um den Wert der Referenz handelt.
  4. Sie übergeben den Wert der Referenz und nicht die Referenz selbst (und nicht das Objekt).
  5. Dieser Wert wird tatsächlich kopiert und an die Methode übergeben.

Im Folgenden (bitte nicht versuchen, dies zu kompilieren / auszuführen):

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

Was geschieht?

  • Die Variable Person wird in Zeile 1 erstellt und ist am Anfang null.
  • Ein neues Personenobjekt wird in Zeile 2, gespeichert im Speicher, und der Variablen erstellt Person erhält den Verweis auf das Objekt Person. Das heißt, seine Adresse. Sagen wir 3bad086a.
  • Die Variable Person Halten Sie die Adresse des Objekts wird an die Funktion in Zeile # 3 übergeben.
  • In Zeile 4 können Sie den Klang der Stille hören
  • Überprüfen Sie den Kommentar in Zeile 5
  • Eine Methode lokale Variable -anotherReferenceToTheSamePersonObject- wird erstellt und dann kommt die Magie in Zeile # 6:
    • Die Variable / Referenz Person wird Bit für Bit kopiert und an übergeben anotherReferenceToTheSamePersonObject innerhalb der Funktion.
    • Es werden keine neuen Instanzen von Person erstellt.
    • Beide "Person" und "anotherReferenceToTheSamePersonObject"halte den gleichen Wert von 3bad086a.
    • Versuchen Sie das nicht, aber person == anotherReferenceToTheSamePersonObject wäre wahr.
    • Beide Variablen haben IDENTISCHE KOPIEN der Referenz und beide beziehen sich auf das gleiche Personenobjekt, das gleiche Objekt auf dem Heap und KEIN KOPIEREN.

Ein Bild sagt mehr als tausend Worte:

Pass by Value

Beachten Sie, dass die Pfeile anotherReferenceToTheSamePersonObject auf das Objekt und nicht auf die variable Person gerichtet sind!

Wenn Sie es nicht verstanden haben, dann vertrauen Sie mir einfach und denken Sie daran, dass es besser ist, das zu sagen Java ist pass-by-Wert. Gut, Pass by Referenzwert. Naja, noch besser ist es Pass-by-Copy-des-Variablen-Wertes! ;)

Jetzt fühlen Sie sich frei, mich zu hassen, aber beachten Sie, dass das gegeben ist Es besteht kein Unterschied zwischen der Übergabe primitiver Datentypen und Objekten wenn man über Methodenargumente spricht.

Sie übergeben immer eine Kopie der Bits des Wertes der Referenz!

  • Wenn es sich um einen primitiven Datentyp handelt, enthalten diese Bits den Wert des primitiven Datentyps selbst.
  • Wenn es ein Objekt ist, enthalten die Bits den Wert der Adresse, die der JVM mitteilt, wie sie zum Objekt gelangt.

Java ist pass-by-value, da Sie in einer Methode das referenzierte Objekt so oft ändern können, wie Sie wollen. Aber egal wie sehr Sie es versuchen, Sie werden nie die übergebene Variable ändern können, die referenziert (nicht p _ _ _ _ _ _ _) das gleiche Objekt egal was!


Die obige changeName-Funktion wird niemals den tatsächlichen Inhalt (die Bitwerte) der übergebenen Referenz ändern können. Mit anderen Worten: changeName kann nicht bewirken, dass Person person auf ein anderes Objekt verweist.


Natürlich kannst du es kurz machen und das einfach sagen Java ist pass-by-value!


651



Java ist immer wertfrei, ohne Ausnahmen, je.

Wie kann es also sein, dass jemand dadurch verwirrt wird und glaubt, dass Java durch Verweis geht, oder denken sie, dass sie ein Beispiel von Java haben, das sich als Referenz verhält? Der entscheidende Punkt ist Java noch nie bietet direkten Zugriff auf die Werte von Objekte selbst, im irgendein Umstände. Der einzige Zugriff auf Objekte erfolgt über a Referenz zu diesem Objekt. Weil Java-Objekte sind immer Zugriff über eine Referenz, anstatt direkt, ist es üblich, über Felder und Variablen zu sprechen und Methodenargumente als sein Objektewenn sie nur pedantisch sind Referenzen auf Objekte. Die Verwirrung ergibt sich aus dieser (streng genommen falschen) Änderung der Nomenklatur.

Also, beim Aufruf einer Methode

  • Für primitive Argumente (int, longusw.), ist der Wert nach bestanden der tatsächliche Wert des Primitiven (zum Beispiel 3).
  • Bei Objekten ist der übergebene Wert der Wert von der Verweis auf das Objekt.

Also wenn du es getan hast doSomething(foo) und public void doSomething(Foo foo) { .. } Die beiden Foos haben es kopiert Verweise diese zeigen auf die gleichen Objekte.

Eine Wertverweisung nach einem Objekt sieht natürlich sehr ähnlich aus (und ist in der Praxis nicht unterscheidbar), wenn ein Objekt als Referenz übergeben wird.


561



Java übergibt Referenzen nach Wert.

Sie können also die übergebene Referenz nicht ändern.


278



Ich habe das Gefühl, dass es nicht super hilfreich ist, über "Pass-by-Reference" oder "Pass-by-Value" zu streiten.

Wenn Sie "Java ist pass-by-whatever (Referenz / Wert)" sagen, erhalten Sie in beiden Fällen keine vollständige Antwort. Hier sind einige zusätzliche Informationen, die hoffentlich helfen werden zu verstehen, was im Speicher passiert.

Absturz auf Stack / Heap, bevor wir zur Java-Implementierung kommen: Die Werte gehen auf eine ordentliche Weise auf und ab, wie ein Stapel Teller in einer Cafeteria. Der Speicher im Heap (auch dynamischer Speicher genannt) ist willkürlich und unorganisiert. Die JVM findet nur Speicherplatz, wo sie kann, und gibt sie frei, da die Variablen, die sie verwenden, nicht mehr benötigt werden.

Okay. Zunächst gehen lokale Primitive auf den Stapel. Also dieser Code:

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

ergibt sich daraus:

primitives on the stack

Wenn Sie ein Objekt deklarieren und instanziieren. Das eigentliche Objekt geht auf den Haufen. Was geht auf den Stapel? Die Adresse des Objekts auf dem Heap. C ++ - Programmierer würden dies einen Zeiger nennen, aber einige Java-Entwickler sind gegen das Wort "Zeiger". Was auch immer. Nur wissen, dass die Adresse des Objekts auf dem Stapel geht.

Wie so:

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

a b*7ch aint one!

Ein Array ist ein Objekt, also geht es auch auf den Heap. Und was ist mit den Objekten im Array? Sie erhalten ihren eigenen Heap-Speicher, und die Adresse jedes Objekts wird innerhalb des Arrays gespeichert.

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

marx brothers

Also, was passiert, wenn Sie eine Methode aufrufen? Wenn Sie ein Objekt übergeben, übergeben Sie die Adresse des Objekts. Einige sagen vielleicht den "Wert" der Adresse und manche sagen, dass es sich nur um einen Verweis auf das Objekt handelt. Dies ist die Entstehung des heiligen Krieges zwischen "Referenz" und "Wert" Befürworter. Was Sie es nennen, ist nicht so wichtig, als dass Sie verstehen, dass die Adresse an das Objekt übergeben wird.

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

Eine Zeichenfolge wird erstellt, und im Heap wird Speicherplatz zugewiesen, und die Adresse der Zeichenfolge wird auf dem Stapel gespeichert und mit dem Bezeichner versehen hisNameDa die Adresse der zweiten Zeichenfolge mit der ersten identisch ist, wird keine neue Zeichenfolge erstellt, und es wird kein neuer Heapspeicher zugewiesen, sondern eine neue Kennung wird auf dem Stapel erstellt. Dann rufen wir an shout(): ein neuer Stapelrahmen wird erstellt und ein neuer Bezeichner name wird erstellt und die Adresse des bereits vorhandenen Strings zugewiesen.

la da di da da da da

Also, Wert, Referenz? Du sagst "Kartoffel".


198



Um nur den Kontrast zu zeigen, vergleichen Sie folgendes C ++ und Java Schnipsel:

In C ++: Hinweis: Schlechter Code - Speicherlecks!  Aber es zeigt den Punkt.

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 hat nur die zwei Arten der Übergabe: nach Wert für integrierte Typen und nach Wert des Zeigers für Objekttypen.


161



Java übergibt Referenzen auf Objekte nach Wert.


145



Ich kann nicht glauben, dass noch niemand Barbara Liskov erwähnt hat. Als sie 1974 CLU entwarf, stieß sie auf das gleiche terminologische Problem, und sie erfand den Begriff Anruf durch Teilen (auch bekannt als Anruf durch Objektfreigabe und Aufruf nach Objekt) für diesen speziellen Fall von "call by value, wo der Wert eine Referenz ist".


135



Im Wesentlichen wirkt sich das Neuzuweisen von Objektparametern nicht auf das Argument aus, z.

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

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

wird ausgedruckt "Hah!" Anstatt von null. Der Grund dafür ist, weil bar ist eine Kopie des Wertes von baz, das ist nur eine Referenz zu "Hah!". Wenn es die eigentliche Referenz selbst wäre, dann foo hätte neu definiert baz zu null.


127