7750 Stimmen

Ist Java "per Referenz übergeben" oder "per Wert übergeben"?

Ich habe immer gedacht, dass Java call-by-reference verwendet. Allerdings habe ich einen Blog-Beitrag gelesen, der behauptet, dass Java call-by-value verwendet. Ich glaube, ich verstehe nicht, welche Unterscheidung der Autor macht.

Was ist die Erklärung?

6 Stimmen

Wir würden eher sagen, dass eine Variable, die "per Referenz übergeben" wird, verändert werden kann. Der Begriff taucht in Lehrbüchern auf, weil Sprachtheoretiker eine Möglichkeit brauchten, um zu unterscheiden, wie Sie primitive Datentypen (int, bool, byte) von komplexen und strukturierten Objekten (Array, Streams, Klasse) behandeln - das heißt, die möglicherweise ungebundene Speicherzuweisung.

6 Stimmen

Ich möchte darauf hinweisen, dass Sie sich in den meisten Fällen keine Gedanken darüber machen müssen. Ich habe viele Jahre lang Java programmiert, bis ich C ++ gelernt habe. Bis zu diesem Zeitpunkt hatte ich keine Ahnung, was Übergabe nach Referenz und Übergabe nach Wert sind. Die intuitive Lösung hat immer für mich funktioniert, deshalb ist Java eine der besten Sprachen für Anfänger. Wenn Sie sich also gerade Sorgen machen, ob Ihre Funktion eine Referenz oder einen Wert benötigt, übergeben Sie sie einfach so, wie sie ist, und es wird alles in Ordnung sein.

73 Stimmen

Java übergibt die Referenz nach Wert.

7069voto

erlando Punkte 6638

Die Begriffe "pass-by-value" und "pass-by-reference" haben in der Informatik spezielle, präzise definierte Bedeutungen. Diese Bedeutungen unterscheiden sich von der Intuition vieler Menschen, wenn sie die Begriffe zum ersten Mal hören. Viel Verwirrung in dieser Diskussion scheint daher zu kommen.

Die Begriffe "pass-by-value" und "pass-by-reference" beziehen sich auf Variablen. Pass-by-value bedeutet, dass der Wert einer Variablen an eine Funktion/Methode übergeben wird. Pass-by-reference bedeutet, dass eine Referenz zu dieser Variablen an die Funktion übergeben wird. Letzteres gibt der Funktion eine Möglichkeit, den Inhalt der Variablen zu ändern.

Nach diesen Definitionen ist Java immer pass-by-value. Leider, wenn wir es mit Variablen zu tun haben, die Objekte halten, haben wir es wirklich mit Objekt-Handles namens Referenzen zu tun, die ebenfalls pass-by-value übergeben werden. Diese Terminologie und Semantik verwirren viele Anfänger leicht.

Es läuft so ab:

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

    // wir übergeben das Objekt an foo
    foo(aDog);
    // die Variable aDog zeigt immer noch auf den Hund "Max", wenn foo(...) zurückkehrt
    aDog.getName().equals("Max"); // true
    aDog.getName().equals("Fifi"); // false
    aDog == oldDog; // true
}

public static void foo(Dog d) {
    d.getName().equals("Max"); // true
    // ändere d innerhalb von foo() so, dass es auf ein neues Hund-Objekt zeigt, das mit dem Namen-Attribut auf "Fifi" gesetzt ist
    d = new Dog("Fifi");
    d.getName().equals("Fifi"); // true
}

In diesem Beispiel wird aDog.getName() weiterhin "Max" zurückgeben. Der Wert von aDog in main wird in der Funktion foo nicht geändert, indem ein neuer Dog mit dem Namen-Attribut auf "Fifi" erstellt wird, weil die Objektreferenz per Wert übergeben wird. Wenn die Objektreferenz per Referenz übergeben worden wäre, würde aDog.getName() in main nach dem Aufruf von foo "Fifi" zurückgeben.

Ebenso:

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

    foo(aDog);
    // wenn foo(...) zurückkehrt, wurde der Name des Hundes auf "Fifi" geändert
    aDog.getName().equals("Fifi"); // true
    // aber es ist immer noch derselbe Hund:
    aDog == oldDog; // true
}

public static void foo(Dog d) {
    d.getName().equals("Max"); // true
    // dies ändert den Namen von d auf "Fifi"
    d.setName("Fifi");
}

In diesem Beispiel ist Fifi der Name des Hundes nach dem Aufruf von foo(aDog), weil der Name des Objekts innerhalb von foo(...) gesetzt wurde. Alle Operationen, die foo auf d durchführt, werden so durchgeführt, dass sie für alle praktischen Zwecke auf aDog durchgeführt werden, aber es ist nicht möglich, den Wert der Variablen aDog selbst zu ändern.

Weitere Informationen zu pass-by-reference und pass-by-value finden Sie in der folgenden Antwort: https://stackoverflow.com/a/430958/6005228. Dort wird ausführlicher die Semantik und Geschichte hinter den beiden erklärt und auch erklärt, warum Java und viele andere moderne Sprachen in bestimmten Fällen scheinbar beides tun.

12 Stimmen

Was passiert also mit "Fifi" im 1. Beispiel? Existiert es nicht mehr, wurde es nie erstellt oder existiert es im Heap, aber ohne eine Referenzvariable im Stack?

0 Stimmen

Was bedeutet der letzte Satz , aber es ist nicht möglich, den Wert der Variablen aDog selbst zu ändern?

110 Stimmen

Für mich bedeutet die Aussage, dass die Referenz eines Objekts nach Wert übergeben wird, dass das Objekt nach Referenz übergeben wird. Ich bin ein Java-Anfänger, aber ich nehme an, dass primitive Daten dagegen nach Wert übergeben wird.

3596voto

Scott Stanchfield Punkte 28549

Ich habe gerade bemerkt, dass du dich auf meinen Artikel bezogen hast.

Das Java-Spezifikation besagt, dass in Java alles nach Wert übergeben wird. Es gibt kein "by-reference"-Konzept in Java.

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

Dog myDog;

kein Dog ist; es handelt sich tatsächlich um einen Pointer zu einem Dog. Die Verwendung des Begriffs "Referenz" in Java ist irreführend und führt hier zu den meisten Missverständnissen. Was sie als "Referenzen" bezeichnen, verhalten sich/fühlen sich eher wie das, was wir in den meisten anderen Sprachen "Pointer" nennen würden.

Das bedeutet, dass, wenn du hast

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

du im Wesentlichen die Adresse des erstellten Dog-Objekts an die Methode foo übergibst.

(Ich sage im Wesentlichen, weil Java-Pointer/Referenzen keine direkten Adressen sind, aber es ist am einfachsten, sie so zu betrachten.)

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

Wenn die Methode definiert wäre als

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

schaun wir mal, was passiert.

  • der Parameter someDog wird auf den Wert 42 gesetzt
  • bei "AAA"
    • someDog wird zum Dog verfolgt, auf den er zeigt (das Dog-Objekt an Adresse 42)
    • dieser Dog (der an Adresse 42) wird gebeten, seinen Namen in Max zu ändern
  • bei "BBB"
    • ein neuer Dog wird erstellt. Nehmen wir an, er ist an Adresse 74
    • wir weisen den Parameter someDog zu 74 zu
  • bei "CCC"
    • someDog wird zum Dog verfolgt, auf den er zeigt (das Dog-Objekt an Adresse 74)
    • dieser Dog (der an Adresse 74) wird gebeten, seinen Namen in Rowlf zu ändern
  • dann kehren wir zurück

Jetzt denken wir darüber nach, was außerhalb der Methode passiert:

Hat sich myDog verändert?

Das ist der Schlüssel.

Im Gedächtnis behalten, dass myDog ein Pointer ist und kein tatsächlicher Dog, lautet die Antwort NEIN. myDog hat immer noch den Wert 42; er zeigt immer noch auf den originalen Dog (aber beachte, dass aufgrund von Zeile "AAA" sein Name jetzt "Max" ist - immer noch der gleiche Dog; der Wert von myDog hat sich nicht geändert.)

Es ist vollkommen gültig, einer Adresse zu folgen und das Ende davon zu ändern; das ändert jedoch nicht die Variable.

Java funktioniert genau wie C. Du kannst einen Pointer zuweisen, den Pointer an eine Methode übergeben, dem Pointer in der Methode folgen und die Daten ändern, auf die verwiesen wird. Der Aufrufer wird jedoch keine Änderungen sehen, die du an der Position machst, auf die der Pointer zeigt. (In einer Sprache mit by-reference-Semantik kann die Methode den Pointer ändern und der Aufrufer wird diese Änderung sehen.)

In C++, Ada, Pascal und anderen Sprachen, die by-reference unterstützen, kannst du tatsächlich die übergebene Variable ändern.

Wenn Java by-reference-Semantik hätte, würde die von uns definierte foo-Methode oben die Position ändern, auf die myDog zeigt, als sie someDog in Zeile BBB zugewiesen hat.

Denke an Referenzparameter als Aliase für die übergebene Variable. Wenn dieser Alias zugewiesen wird, wird also auch die übergebene Variable zugewiesen.

Aktualisierung

Einige Diskussionen in den Kommentaren erfordern eine Klärung...

In C kannst du folgendes schreiben

void swap(int *x, int *y) {
    int t = *x;
    *x = *y;
    *y = t;
}

int x = 1;
int y = 2;
swap(&x, &y);

Dies ist kein spezieller Fall in C. Beide Sprachen verwenden Pass-by-Value-Semantik. Hier erstellt die Aufrufstelle zusätzliche Datenstrukturen, um der Funktion den Zugriff und die Manipulation der Daten zu ermöglichen.

Der Funktion werden Zeiger auf die Daten übergeben, und sie folgt diesen Zeigern, um auf die Daten zuzugreifen und sie zu modifizieren.

Ein ähnlicher Ansatz in Java, wo der Aufrufer eine unterstützende Struktur einrichtet, könnte sein:

void swap(int[] x, int[] y) {
    int temp = x[0];
    x[0] = y[0];
    y[0] = temp;
}

int[] x = {1};
int[] y = {2};
swap(x, y);

(oder wenn du beide Beispiele zeigen möchtest, die Funktionen der anderen Sprache nicht haben, erstelle eine änderbare IntWrapper-Klasse anstelle der Arrays)

In diesen Fällen simulieren sowohl C als auch Java Pass-by-Reference. Beide übergeben immer noch Werte (Zeiger auf ints oder Arrays) und folgen diesen Zeigern innerhalb der aufgerufenen Funktion, um die Daten zu manipulieren.

Pass-by-Reference bezieht sich auf die Funktionsdeklaration/-definition und wie sie ihre Parameter behandelt. Referenzsemantik gilt für jeden Aufruf dieser Funktion, und die Aufrufstelle muss nur Variablen übergeben, ohne zusätzliche Datenstruktur.

Diese Simulationen erfordern die Zusammenarbeit von aufrufender Stelle und Funktion. Sicherlich ist es nützlich, aber es ist immer noch Pass-by-Value.

0 Stimmen

Kleine Klarstellungsfrage zum obigen Beispiel: Impliziert dies bei der Erstellung eines neuen Hundes bei BBB an der Adresse 72, dass bei der Rückkehr der erstellte Hund an 72 und sein Wert verloren gehen und auf 42 zurückgesetzt werden?

3 Stimmen

0 Stimmen

@ebresie Meist ja (Ich werde das "meist" gleich erklären). Der einzige Zeiger auf den neuen Hund bei 74 (ich nehme an, Sie meinten 74 und nicht 72) ist der Parameter für die Foo-Funktion. Wenn Foo zurückkehrt, werden alle Parameter vom Stapel entfernt, sodass nichts mehr auf 72 zeigt und es vom Garbage Collector eingesammelt werden kann. Ich sage "meist", da kein "Rückgängig machen" stattfindet; der Zeiger meinHund im Aufrufer zeigte die ganze Zeit auf 42 und hat sich nie geändert, egal was in der Funktion passiert ist, daher kein "Rückgängigmachen".

2125voto

Eng.Fouad Punkte 110730

Java gibt Argumente immer by value weiter, NICHT by reference.


Ich erkläre das anhand eines Beispiels:

public class Main {

     public static void main(String[] args) {
          Foo f = new Foo("f");
          changeReference(f); // Der Verweis wird nicht geändert!
          modifyReference(f); // Das Objekt, auf das sich der Verweis "f" bezieht, wird geändert!
     }

     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. Einen Verweis namens f vom Typ Foo deklarieren und ihm ein neues Objekt des Typs Foo mit einem Attribut "f" zuweisen.

    Foo f = new Foo("f");

    Bildbeschreibung hier eingeben

  2. Vom Methodenseite her wird ein Verweis vom Typ Foo mit dem Namen a deklariert und anfangs null zugewiesen.

    public static void changeReference(Foo a)

    Bildbeschreibung hier eingeben

  3. Wenn Sie die Methode changeReference aufrufen, wird der Verweis a dem als Argument übergebenen Objekt zugewiesen.

    changeReference(f);

    Bildbeschreibung hier eingeben

  4. Einen Verweis namens b vom Typ Foo deklarieren und ihm ein neues Objekt des Typs Foo mit einem Attribut "b" zuweisen.

    Foo b = new Foo("b");

    Bildbeschreibung hier eingeben

  5. a = b weist dem Verweis a, nicht f, des Objekts mit dem Attribut "b" eine neue Zuweisung zu.

    Bildbeschreibung hier eingeben

  6. Wenn Sie die Methode modifyReference(Foo c) aufrufen, wird ein Verweis c erstellt und dem Objekt mit dem Attribut "f" zugewiesen.

    Bildbeschreibung hier eingeben

  7. c.setAttribute("c"); ändert das Attribut des Objekts, auf das der Verweis cf

    Bildbeschreibung hier eingeben

74 Stimmen

Java übergibt immer Argumente nach Wert, aber was Sie nach Wert übergeben, ist eine Referenz auf ein Objekt, keine Kopie des Objekts. Einfach, oder?

1 Stimmen

Ich hoffe, dass das Herbert Schildt Buch, aus dem ich Java gelernt habe, dies gelehrt hat.

1 Stimmen

"Objekt nicht per Verweis", wirklich?

858voto

SCdF Punkte 53953

Java ist immer pass-by-value, ohne Ausnahmen, jemals.

Wie kann es also sein, dass jemand überhaupt verwirrt ist und glaubt, dass Java by reference ist, oder denkt, er habe ein Beispiel dafür, dass Java als by reference handelt? Der entscheidende Punkt ist, dass Java niemals direkt auf die Werte der Objekte selbst zugreift, unter keinen Umständen. Der einzige Zugriff auf Objekte erfolgt über eine Referenz zu diesem Objekt. Da Java-Objekte immer über eine Referenz und nicht direkt aufgerufen werden, ist es üblich, von Feldern und Variablen und Methodenargumenten als Objekten zu sprechen, wenn sie pedantisch betrachtet nur Referenzen zu Objekten sind. Die Verwirrung kommt von dieser (streng genommen falschen) Änderung in der Nomenklatur.

Also, wenn Sie eine Methode aufrufen

  • Für primitiv Argumente (int, long, etc.) ist das Passieren nach Wert der tatsächliche Wert des Primitivs (zum Beispiel 3).
  • Für Objekte ist das Passieren nach Wert der Wert der Referenz zum Objekt.

Also, wenn Sie doSomething(foo) haben und public void doSomething(Foo foo) { .. } die zwei Foos haben kopierte Referenzen, die auf die gleichen Objekte zeigen.

Natürlich sieht das Passieren nach Wert einer Referenz zu einem Objekt sehr ähnlich aus (und ist in der Praxis nicht zu unterscheiden) vom Passieren eines Objekts per Referenz.

0 Stimmen

JVMS 2.2 macht dies ziemlich klar: Es gibt... zwei Arten von Werten, die in Variablen gespeichert, als Argumente übergeben, von Methoden zurückgegeben und verarbeitet werden können: primitive Werte und Referenzwerte." Objektreferenzen sind Werte. Alles wird nach Wert übergeben.

0 Stimmen

Die operative Implikation: f(x) (Übergabe einer Variablen) weist niemals direkt x zu. Es gibt keine Variable-Adresse (Alias), die übergeben wird. Ein solider Sprachdesign-Entscheid.

800voto

Marsellus Wallace Punkte 16941

Dies wird Ihnen einige Einblicke geben, wie Java wirklich funktioniert, sodass Sie bei Ihrer nächsten Diskussion über Java, ob es nach Referenz oder nach Wert übergeben wird, einfach lächeln werden :-)

Schritt eins, bitte löschen Sie aus Ihrem Gedächtnis das Wort, das mit 'p' beginnt "_ _ _ _ _ _ _", insbesondere wenn Sie aus anderen Programmiersprachen kommen. Java und 'p' können nicht im gleichen Buch, Forum oder sogar Text geschrieben werden.

Schritt zwei, denken Sie daran, dass, wenn Sie ein Objekt an eine Methode übergeben, Sie die Objektreferenz übergeben und nicht das Objekt selbst.

  • Schüler: Meister, bedeutet das, dass Java nach Referenz übergeben wird?
  • Meister: Heuschrecke, Nein.

Denken Sie jetzt darüber nach, was eine Objektreferenz/Variable tut/ist:

  1. Eine Variable enthält die Bits, die dem JVM mitteilen, wie es zum referenzierten Objekt im Speicher (Heap) gelangt.
  2. Beim Übergeben von Argumenten an eine Methode übergeben Sie NICHT die Referenzvariable, sondern eine Kopie der Bits in der Referenzvariable. So ähnlich wie dies: 3bad086a. 3bad086a repräsentiert einen Weg, um auf das übergebene Objekt zuzugreifen.
  3. Sie übergeben also nur 3bad086a, was der Wert der Referenz ist.
  4. Sie übergeben den Wert der Referenz und nicht die Referenz selbst (und nicht das Objekt).
  5. Dieser Wert wird tatsächlich KOPIERT und der Methode übergeben.

In dem folgenden (bitte versuchen Sie nicht, dies zu kompilieren/auszuführen...):

1. Person person;
2. person = new Person("Tom");
3. changeName(person);
4.
5. //Ich habe unten Person person nicht als Argument verwendet, um nett zu sein
6. static void changeName(Person anotherReferenceToTheSamePersonObject) {
7.     anotherReferenceToTheSamePersonObject.setName("Jerry");
8. }

Was passiert?

  • Die Variable person wird in Zeile #1 erstellt und ist zu Beginn null.
  • Ein neues Person-Objekt wird in Zeile #2 erstellt, im Speicher gespeichert, und der Variable person wird die Referenz auf das Personenobjekt gegeben. Das bedeutet, die Adresse. Sagen wir 3bad086a.
  • Die Variable person, die die Adresse des Objekts enthält, wird an die Funktion in Zeile #3 übergeben.
  • In Zeile #4 können Sie dem Klang der Stille lauschen
  • Beachten Sie den Kommentar in Zeile #5
  • Eine Methodenlokale Variable - anotherReferenceToTheSamePersonObject - wird erstellt und dann kommt die Magie in Zeile #6:
    • Die Variable/Referenz person wird bitweise kopiert und an anotherReferenceToTheSamePersonObject innerhalb der Funktion übergeben.
    • Es werden keine neuen Instanzen von Person erstellt.
    • Sowohl "person" als auch "anotherReferenceToTheSamePersonObject" enthalten den gleichen Wert von 3bad086a.
    • Versuchen Sie das nicht, aber person==anotherReferenceToTheSamePersonObject wäre wahr.
    • Beide Variablen haben IDENTISCHE KOPIEN der Referenz und sie verweisen beide auf dasselbe Personenobjekt, dasselbe Objekt im Heap und NICHT EINE KOPIE.

Ein Bild sagt mehr als tausend Worte:

Nach Wert übergeben

Beachten Sie, dass die Pfeile von anotherReferenceToTheSamePersonObject auf das Objekt und nicht auf die Variable person zeigen!

Wenn Sie es nicht verstanden haben, dann vertrauen Sie mir einfach und merken Sie sich, dass es besser ist zu sagen, dass Java nach Wert übergeben wird. Nun, nach Wert der Referenz übergeben wird. Oh gut, noch besser ist übergeben durch Kopie des Variablenwerts! ;)

Jetzt können Sie mich ruhig hassen, aber beachten Sie, dass angesichts dessen kein Unterschied besteht, ob primitive Datentypen oder Objekte übergeben werden, wenn es um Methodenargumente geht.

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

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

Java wird nach Wert übergeben, weil Sie innerhalb einer Methode das referenzierte Objekt so oft ändern können, wie Sie möchten, aber egal wie sehr Sie es versuchen, Sie werden nie in der Lage sein, die übergebene Variable zu ändern, die weiterhin auf dasselbe Objekt verweist (nicht p _ _ _ _ _ _ _) egal was passiert!


Die oben genannte changeName-Funktion wird niemals den tatsächlichen Inhalt (die Bitwerte) der übergebenen Referenz ändern können. Anders ausgedrückt kann changeName Person person nicht dazu bringen, auf ein anderes Objekt zu verweisen.


Natürlich können Sie es kurz fassen und einfach sagen, dass Java nach Wert übergeben wird!

0 Stimmen

Ich habe das versucht:
Datei datei = new Datei("C:/"); ändereDatei(datei); System.out.println(datei.getAbsolutePath()); } public static void ändereDatei(Datei d) { d = new Datei("D:/"); }

0 Stimmen

Schöne Erklärung, danke mein Herr.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X