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.

173voto

Jörg W Mittag Punkte 349574

Ich kann nicht glauben, dass noch niemand Barbara Liskov erwähnt hat. Als sie 1974 CLU entwarf, stieß sie auf dasselbe Terminologieproblem und erfand den Begriff call by sharing (auch bekannt als call by object-sharing und call by object) für diesen speziellen Fall von "Aufruf nach Wert, bei dem der Wert eine Referenz ist".

0 Stimmen

:) Ein weiterer Begriff, der die Verwirrung rund um die Java-Insel füttert, nur weil es politisch inkorrekt ist zu sagen "Ein Objekt wird per Referenz übergeben, wie wir es im Stapel finden".

141voto

JacquesB Punkte 40790

Der Kern der Sache ist, dass das Wort Verweis im Ausdruck "Übergabe per Verweis" etwas völlig anderes als die übliche Bedeutung des Wortes Verweis in Java bedeutet.

In Java bedeutet Verweis normalerweise eine Referenz auf ein Objekt. Aber die Fachbegriffe Übergabe per Verweis/Wert aus der Programmiersprachentheorie beziehen sich auf einen Verweis auf die Speicherzelle, die die Variable hält, was etwas vollkommen anderes ist.

0 Stimmen

Ja, eine Objektreferenz ist technisch gesehen ein Handle, noch nicht die Adresse, und daher sogar noch einen Schritt weiter als "by value".

103voto

Ganesh Punkte 914

Java wird immer nach Wert übergeben, nicht nach Referenz

Zunächst müssen wir verstehen, was "nach Wert" und "nach Referenz" bedeuten.

Nach Wert bedeutet, dass eine Kopie des tatsächlichen Parameterwerts im Speicher erstellt wird, der übergeben wird. Dies ist eine Kopie des Inhalts des tatsächlichen Parameters.

Nach Referenz (auch nach Adresse genannt) bedeutet, dass eine Kopie der Adresse des tatsächlichen Parameters gespeichert wird.

Manchmal kann Java die Illusion von nach Referenz übergeben erzeugen. Schauen wir uns anhand des folgenden Beispiels an, wie es funktioniert:

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

Die Ausgabe dieses Programms ist:

changevalue

Verstehen wir Schritt für Schritt:

Test t = new Test();

Wie wir alle wissen, wird ein Objekt im Heap erstellt und der Referenzwert t zurückgegeben. Angenommen, der Wert von t ist 0x100234 (wir kennen nicht den tatsächlichen JVM-internen Wert, dies ist nur ein Beispiel).

erste Illustration

new PassByValue().changeValue(t);

Beim Übergeben der Referenz t an die Funktion wird nicht direkt der tatsächliche Referenzwert des Test-Objekts übergeben, sondern es wird eine Kopie von t erstellt und dann an die Funktion übergeben. Da es sich um eine Übergabe nach Wert handelt, wird eine Kopie der Variablen übergeben anstelle des tatsächlichen Verweises darauf. Da wir gesagt haben, dass der Wert von t 0x100234 war, werden sowohl t als auch f den gleichen Wert haben und daher auf dasselbe Objekt zeigen.

zweite Illustration

Wenn Sie etwas in der Funktion mit der Referenz f ändern, werden die vorhandenen Inhalte des Objekts geändert. Deshalb haben wir die Ausgabe changevalue erhalten, die in der Funktion aktualisiert wurde.

Um dies klarer zu verstehen, betrachten Sie das folgende Beispiel:

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

Wird dies eine NullPointerException auslösen? Nein, weil nur eine Kopie der Referenz übergeben wird. Im Falle der Übergabe nach Referenz hätte es eine NullPointerException auslösen können, wie unten gezeigt:

dritte Illustration

102voto

Srle Punkte 10072

Im Java ist alles eine Referenz, also wenn Sie etwas wie haben: Point pnt1 = new Point(0,0); Java macht folgendes:

  1. Erstellt ein neues Point-Objekt
  2. Erstellt eine neue Point-Referenz und initialisiert diese Referenz auf Punkt (beziehen auf) das zuvor erstellte Point-Objekt.
  3. Von hier aus, während des Lebenszyklus des Point-Objekts, haben Sie Zugriff auf dieses Objekt über die pnt1-Referenz. Also kann man sagen, dass Sie in Java das Objekt durch seine Referenz manipulieren.

Bildbeschreibung hier eingeben

Java übergibt Methodenargumente nicht per Referenz; es übergibt sie per Wert. Ich werde ein Beispiel von dieser Seite verwenden:

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

Ablauf des Programms:

Point pnt1 = new Point(0,0);
Point pnt2 = new Point(0,0);

Erstellung von zwei unterschiedlichen Point-Objekten mit zwei verschiedenen zugehörigen Referenzen. Bildbeschreibung hier eingeben

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

Wie erwartet, wird die Ausgabe sein:

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

An dieser Stelle kommt 'Übergabe nach Wert' ins Spiel...

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

Die Referenzen pnt1 und pnt2 werden an die tricky-Methode per Wert übergeben, was bedeutet, dass Ihre Referenzen pnt1 und pnt2 jetzt ihre Kopien namens arg1 und arg2 haben. Also pnt1 und arg1 zeigen auf dasselbe Objekt. (Das gilt auch für pnt2 und arg2) Bildbeschreibung hier eingeben

In der tricky Methode:

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

Bildbeschreibung hier eingeben

Weiter in der tricky Methode

Point temp = arg1;
arg1 = arg2;
arg2 = temp;

Hier erstellen Sie zunächst eine neue temp Point-Referenz, die auf dieselbe Stelle wie die arg1 Referenz zeigt. Dann verschieben Sie die Referenz arg1 an die Stelle der arg2 Referenz. Schließlich wird arg2 auf die gleiche Stelle wie temp zeigen.

Bildbeschreibung hier eingeben

Von hier aus ist der Gültigkeitsbereich der tricky Methode vorbei und Sie haben keinen Zugriff mehr auf die Referenzen: arg1, arg2, temp. Aber wichtig ist, dass alles, was Sie mit diesen Referenzen tun, wenn sie 'im Leben' sind, das Objekt, auf das sie zeigen, dauerhaft beeinflussen wird.

Also, nach Ausführung der Methode tricky, wenn Sie zum main zurückkehren, haben Sie diese Situation: Bildbeschreibung hier eingeben

Also wird die vollständige Ausführung des Programms folgendermaßen aussehen:

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

1 Stimmen

Die Hälfte der Miete: "Alles" ist "Objekte" in deinem Beitrag.

1 Stimmen

Sie schrieben: "In Java ist alles eine Referenz." Dies ist nicht korrekt. Nur Objekte sind Referenzen. Primitivtypen sind es nicht. Dies meinte @SamGinrich mit seinem Kommentar.

76voto

karatedog Punkte 2312

Eine Referenz ist immer ein Wert, wenn sie dargestellt wird, unabhängig von der verwendeten Sprache.

Um einen Blick außerhalb des üblichen Rahmens zu werfen, betrachten wir Assembly oder das Speichermanagement auf niedriger Ebene. Auf der CPU-Ebene wird eine Referenz zu allem sofort zu einem Wert, wenn sie in den Speicher oder in eines der CPU-Register geschrieben wird. (Deshalb ist Zeiger eine gute Definition. Es ist ein Wert, der gleichzeitig einen Zweck hat).

Daten im Speicher haben einen Ort und an diesem Ort befindet sich ein Wert (Byte, Wort, was auch immer). In Assembly haben wir eine praktische Lösung, um einem bestimmten Ort (auch Variable genannt) einen Namen zu geben, aber beim Kompilieren des Codes ersetzt der Assembler einfach den Namen durch den festgelegten Ort, genauso wie Ihr Browser Domainnamen durch IP-Adressen ersetzt.

Technisch gesehen ist es im Grundlagenbereich in keiner Sprache technisch unmöglich, eine Referenz zu irgendetwas zu übergeben, ohne sie zu repräsentieren (wenn sie sofort zu einem Wert wird).

Angenommen, wir haben eine Variable Foo, ihr Ort befindet sich im 47. Byte im Speicher und ihr Wert beträgt 5. Wir haben eine andere Variable Ref2Foo, die sich im 223. Byte im Speicher befindet, und ihr Wert ist 47. Diese Ref2Foo könnte eine technische Variable sein, die nicht explizit vom Programm erstellt wurde. Wenn man sich nur die Zahlen 5 und 47 ohne weitere Informationen ansieht, sieht man nur zwei Werte. Wenn Sie sie als Referenzen verwenden, um zu 5 zu gelangen, müssen Sie folgendes tun:

(Name)[Ort] -> [Wert am Ort]
---------------------
(Ref2Foo)[223]  -> 47
(Foo)[47]       -> 5

So funktionieren Sprungtabellen.

Wenn wir eine Methode/Funktion/Prozedur mit dem Wert von Foo aufrufen möchten, gibt es einige mögliche Möglichkeiten, die Variable an die Methode zu übergeben, je nach Sprache und ihren verschiedenen Methoden-Aufrufmodi:

  1. 5 wird in eines der CPU-Register kopiert (z. B. EAX).
  2. 5 wird auf den Stapel (Stack) gepusht.
  3. 47 wird in eines der CPU-Register kopiert.
  4. 47 wird auf den Stapel (Stack) gepusht.
  5. 223 wird in eines der CPU-Register kopiert.
  6. 223 wird auf den Stapel (Stack) gepusht.

In allen oben genannten Fällen wurde ein Wert - eine Kopie eines vorhandenen Werts - erstellt, nun liegt es an der empfangenden Methode, damit umzugehen. Wenn Sie in der Methode "Foo" schreiben, wird sie entweder aus EAX ausgelesen oder automatisch dereferenziert oder doppelt dereferenziert, der Prozess hängt davon ab, wie die Sprache funktioniert und/oder was der Typ von Foo vorgibt. Dies ist für den Entwickler verborgen, bis er den Dereferenzierungsprozess umgeht. Eine Referenz ist also ein Wert, wenn sie dargestellt wird, weil eine Referenz ein Wert ist, der verarbeitet werden muss (auf Sprachebene).

Jetzt haben wir Foo an die Methode übergeben:

  • In Fall 1. und 2., wenn Sie Foo ändern (Foo = 9), betrifft dies nur den lokalen Bereich, da Sie eine Kopie des Werts haben. Von innerhalb der Methode aus können wir nicht einmal feststellen, wo im Speicher das originale Foo sich befand.
  • In Fall 3. und 4., wenn Sie die Standard-Sprachkonstrukte verwenden und Foo ändern (Foo = 11), könnte sich Foo global ändern (das hängt von der Sprache ab, z. B. Java oder ähnlich wie bei Pascals procedure findMin(x, y, z: integer;var m: integer);). Allerdings, wenn die Sprache es Ihnen erlaubt, den Dereferenzierungsprozess zu umgehen, können Sie 47 ändern, sagen wir zu 49. Zu diesem Zeitpunkt scheint es, als ob Foo geändert wurde, wenn man es liest, weil Sie den lokalen Zeiger darauf geändert haben. Und wenn Sie dieses Foo innerhalb der Methode ändern würden (Foo = 12), würden Sie wahrscheinlich die Ausführung des Programms durcheinander bringen (ein sogenannter Segfault), weil Sie in einen anderen Speicher schreiben würden als erwartet, Sie könnten sogar einen Bereich ändern, der dazu bestimmt ist, ausführbaren Code zu halten, und das Schreiben darin würde den laufenden Code ändern (Foo befindet sich jetzt nicht mehr an der Stelle 47). ABER der Wert von Foo, 47, hat sich nicht global geändert, nur der in der Methode, da 47 auch eine Kopie in der Methode war.
  • In Fall 5. und 6., wenn Sie 223 in der Methode ändern, entsteht das gleiche Chaos wie in 3. oder 4. (ein Zeiger, der auf einen nun ungültigen Wert zeigt, der wieder als Zeiger verwendet wird), aber das ist immer noch ein lokales Problem, da 223 kopiert wurde. Wenn Sie jedoch in der Lage sind, Ref2Foo zu dereferenzieren (also 223), zu erreichen und den Zeigerwert 47 zu ändern, sagen wir zu 49, wird sich Foo global ändern, weil in diesem Fall die Methoden eine Kopie von 223 erhalten haben, aber das referenzierte 47 existiert nur einmal, und die Änderung auf 49 wird dazu führen, dass jede doppelte Dereferenzierung von Ref2Foo auf einen falschen Wert verweist.

Wenn wir uns an belanglosen Details aufhalten, selbst Sprachen, die per Referenz übergeben, übergeben Werte an Funktionen, aber diese Funktionen wissen, dass sie sie für Dereferenzierungszwecke verwenden müssen. Dieses Weitergeben der Referenz als Wert wird dem Programmierer nur verborgen, da es praktisch nutzlos ist und die Terminologie nur Referenzübergabe ist.

Strenges Wertübergabe ist ebenfalls nutzlos, da es bedeuten würde, dass ein 100 Mbyte-Array jedes Mal kopiert werden müsste, wenn wir eine Methode mit dem Array als Argument aufrufen, deshalb kann Java nicht streng wertübergeben sein. Jede Sprache würde eine Referenz zu diesem riesigen Array übergeben (als Wert) und entweder einen Kopierschreibmechanismus verwenden, wenn dieses Array lokal in der Methode geändert werden kann, oder der Methode (wie Java es tut) erlauben, das Array global zu ändern (aus der Sicht des Aufrufers), und einige Sprachen erlauben, den Wert der Referenz selbst zu ändern.

Kurz gesagt und in der eigenen Terminologie von Java ausgedrückt, ist Java wertübergeben, wobei der Wert entweder ein echter Wert oder ein Wert ist, der eine Referenz darstellt.

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