Ist Java "Pass-by-Reference" oder "Pass-by-Value"?

6580

Ich dachte immer, Java sei eine Referenz .

Ich habe jedoch einige Blog-Beiträge gesehen (zum Beispiel diesen Blog ), die behaupten, dass dies nicht der Fall ist.

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

Was ist die Erklärung?

Benutzer4315
quelle
706
Ich glaube, dass ein Großteil der Verwirrung in dieser Frage damit zusammenhängt, dass verschiedene Personen unterschiedliche Definitionen des Begriffs "Referenz" haben. Personen mit einem C ++ - Hintergrund gehen davon aus, dass "Referenz" bedeuten muss, was es in C ++ bedeutet. Personen mit einem C-Hintergrund gehen davon aus, dass "Referenz" in ihrer Sprache mit "Zeiger" identisch sein muss, und so weiter. Ob es richtig ist zu sagen, dass Java als Referenz übergeben wird, hängt wirklich davon ab, was unter "Referenz" zu verstehen ist.
Schwerkraft
119
Ich versuche, die im Artikel über die Bewertungsstrategie enthaltene Terminologie konsequent zu verwenden . Es sollte beachtet werden, dass, obwohl der Artikel darauf hinweist, dass die Begriffe je nach Community sehr unterschiedlich sind, betont wird, dass sich die Semantik für call-by-valueund call-by-referencein sehr entscheidender Weise unterscheidet . (Ich persönlich bevorzuge die Verwendung call-by-object-sharingüber diese Tage call-by-value[-of-the-reference], da dies die Semantik auf hohe Ebene beschreibt und schafft keinen Konflikt mit call-by-value, das ist die zugrunde liegende Implementierung.)
59
@Gravity: Kannst du deinen Kommentar auf eine RIESIGE Werbetafel setzen oder so? Das ist das ganze Problem auf den Punkt gebracht. Und es zeigt, dass das Ganze Semantik ist. Wenn wir uns nicht auf die
Basisdefinition
30
Ich denke, die Verwirrung ist "Referenzübergabe" gegenüber "Referenzsemantik". Java ist ein Pass-by-Value mit Referenzsemantik.
Spraff
11
@Gravity, obwohl Sie absolut richtig sind, dass Leute aus C ++ instinktiv eine andere Intuition in Bezug auf den Begriff "Referenz" haben, glaube ich persönlich, dass der Körper mehr im "by" begraben ist. "Pass by" ist insofern verwirrend, als es sich absolut von "Passing a" in Java unterscheidet. In C ++ ist dies jedoch umgangssprachlich nicht der Fall. In C ++ können Sie "Übergeben einer Referenz" sagen, und es wird davon ausgegangen, dass der swap(x,y)Test bestanden wird .

Antworten:

5840

Java ist immer ein Pass-by-Value . Wenn wir den Wert eines Objekts übergeben, übergeben wir leider den Verweis darauf. Dies ist für Anfänger verwirrend.

Es geht so:

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

    // 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
    aDog == oldDog; // true
}

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ückgegeben "Max". Der Wert aDoginnerhalb mainwird in der Funktion foomit nicht geändert, Dog "Fifi"da die Objektreferenz als Wert übergeben wird. Wenn es als Referenz übergeben wurde, dann wird die aDog.getName()in mainzurückkommen würde "Fifi"nach dem Aufruf foo.

Gleichfalls:

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

    foo(aDog);
    // when foo(...) returns, the name of the dog has been changed to "Fifi"
    aDog.getName().equals("Fifi"); // true
    // but it is still the same dog:
    aDog == oldDog; // 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 Fifiist der Name des Hundes nach dem Aufruf von angegeben, foo(aDog)da der Name des Objekts innerhalb von festgelegt wurde foo(...). Alle Operationen, foodie ausgeführt werden, dsind so, dass sie für alle praktischen Zwecke ausgeführt werden aDog, es ist jedoch nicht möglich, den Wert der Variablen aDogselbst zu ändern .

erlando
quelle
263
Verwechselt es das Problem nicht leicht mit internen Details? Es gibt keinen konzeptionellen Unterschied zwischen "Übergeben einer Referenz" und "Übergeben des Werts einer Referenz", vorausgesetzt, Sie meinen "den Wert des internen Zeigers auf das Objekt".
izb
383
Aber es gibt einen subtilen Unterschied. Schauen Sie sich das erste Beispiel an. Wenn es nur als Referenz übergeben würde, wäre aDog.name "Fifi". Dies ist nicht der Fall. Die Referenz, die Sie erhalten, ist eine Wertreferenz, die beim Beenden der Funktion wiederhergestellt wird, wenn sie überschrieben wird.
Erlando
300
@ Lorenzo: Nein, in Java wird alles als Wert übergeben. Grundelemente werden als Wert übergeben, und Objektreferenzen werden als Wert übergeben. Die Objekte selbst werden niemals an eine Methode übergeben, aber die Objekte befinden sich immer im Heap und nur ein Verweis auf das Objekt wird an die Methode übergeben.
Esko Luontola
294
Mein Versuch, das Passieren von Objekten gut zu visualisieren: Stellen Sie sich einen Ballon vor. Das Aufrufen eines FXN ist wie das Binden einer zweiten Zeichenfolge an den Ballon und das Übergeben der Linie an den FXN. parameter = new Balloon () schneidet diese Zeichenfolge und erstellt einen neuen Ballon (dies hat jedoch keine Auswirkungen auf den ursprünglichen Ballon). parameter.pop () wird es dennoch platzen lassen, da es der Zeichenfolge zu derselben ursprünglichen Sprechblase folgt. Java wird als Wert übergeben, aber der übergebene Wert ist nicht tief, sondern befindet sich auf der höchsten Ebene, dh als Grundelement oder Zeiger. Verwechseln Sie dies nicht mit einem tiefen Pass-by-Wert, bei dem das Objekt vollständig geklont und übergeben wird.
Dhackner
150
Verwirrend ist, dass Objektreferenzen tatsächlich Zeiger sind. Am Anfang nannte SUN sie Zeiger. Dann teilte das Marketing mit, dass "Zeiger" ein schlechtes Wort sei. In NullPointerException wird jedoch weiterhin die "richtige" Nomenklatur angezeigt.
Prof. Falken Vertrag verletzt
3035

Ich habe gerade bemerkt, dass Sie auf meinen Artikel verwiesen haben .

Die Java-Spezifikation besagt, dass alles in Java als Wert übergeben wird. In Java gibt es kein "Pass-by-Reference".

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

Dog myDog;

ist kein Hund; Es ist eigentlich ein Hinweis auf einen Hund.

Was das bedeutet, ist, wenn Sie haben

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

Sie übergeben im Wesentlichen die Adresse des erstellten DogObjekts an die fooMethode.

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

Angenommen, das DogObjekt befindet sich an der Speicheradresse 42. Dies bedeutet, dass wir 42 an die Methode übergeben.

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
}

Schauen wir uns an, was passiert.

  • Der Parameter someDogwird auf den Wert 42 gesetzt
  • in der Zeile "AAA"
    • someDogwird gefolgt, auf das Doges zeigt (das DogObjekt an Adresse 42)
    • dass Dog(der an Adresse 42) gebeten wird, seinen Namen in Max zu ändern
  • in der Zeile "BBB"
    • ein neues Dogwird erstellt. Nehmen wir an, er ist an der Adresse 74
    • Wir weisen den Parameter someDog74 zu
  • in der Zeile "CCC"
    • someDog wird bis zu dem DogPunkt gefolgt, auf den es zeigt (das DogObjekt an Adresse 74).
    • dieser Dog(der an Adresse 74) wird gebeten, seinen Namen in Rowlf zu ändern
  • dann kehren wir zurück

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

Hat sich myDoggeändert?

Da ist der Schlüssel.

Wenn man bedenkt, dass dies myDogein Zeiger und kein tatsächlicher ist Dog, lautet die Antwort NEIN. myDoghat noch den Wert 42; Es zeigt immer noch auf das Original Dog(aber beachten Sie, dass der Name aufgrund der Zeile "AAA" jetzt "Max" lautet - immer noch derselbe Hund; myDogder Wert hat sich nicht geändert.)

Es ist absolut gültig, einer Adresse zu folgen und zu ändern, was am Ende steht. das ändert die Variable jedoch nicht.

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

In C ++, Ada, Pascal und anderen Sprachen, die die Referenzübergabe unterstützen, können Sie die übergebene Variable tatsächlich ändern.

Wenn Java eine Referenzpass-Semantik foohätte, hätte sich die oben definierte Methode geändert, wohin sie myDogzeigte, als sie someDogin Zeile BBB zugewiesen wurde .

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

Scott Stanchfield
quelle
164
Aus diesem Grund ist der übliche Refrain "Java hat keine Zeiger" so irreführend.
Beska
134
Du liegst falsch, imho. "Wenn man bedenkt, dass myDog ein Zeiger und kein tatsächlicher Hund ist, lautet die Antwort NEIN. MyDog hat immer noch den Wert 42; es zeigt immer noch auf den ursprünglichen Hund." myDog hat den Wert 42, aber sein Namensargument enthält jetzt "Max" anstelle von "Rover" in der Zeile // AAA.
Özgür
180
Denken Sie so darüber nach. Jemand hat die Adresse von Ann Arbor, MI (meine Heimatstadt, GO BLUE!) Auf einem Zettel namens "annArborLocation". Sie kopieren es auf ein Blatt Papier namens "myDestination". Sie können zu "myDestination" fahren und einen Baum pflanzen. Möglicherweise haben Sie an diesem Ort etwas an der Stadt geändert, aber es ändert nichts an der LAT / LON, die auf beiden Papieren geschrieben wurde. Sie können LAT / LON in "myDestination" ändern, "annArborLocation" jedoch nicht. Hilft das?
Scott Stanchfield
43
@ Scott Stanchfield: Ich habe Ihren Artikel vor ungefähr einem Jahr gelesen und er hat mir wirklich geholfen, die Dinge zu klären. Vielen Dank! Darf ich demütig eine kleine Ergänzung vorschlagen: Sie sollten erwähnen, dass es tatsächlich einen bestimmten Begriff gibt, der diese Form von "Call by Value, bei der der Wert eine Referenz ist" beschreibt, die von Barbara Liskov erfunden wurde, um die Bewertungsstrategie ihrer CLU-Sprache in zu beschreiben 1974, um Verwirrung zu vermeiden, wie sie in Ihrem Artikel angesprochen wird: Call by Sharing (manchmal Call by Object-Sharing oder einfach Call by Object genannt ), was die Semantik ziemlich perfekt beschreibt.
Jörg W Mittag
29
@Gevorg - Die C / C ++ - Sprachen besitzen nicht das Konzept eines "Zeigers". Es gibt andere Sprachen, die Zeiger verwenden, jedoch nicht die gleichen Arten der Manipulation von Zeigern zulassen, die C / C ++ zulässt. Java hat Zeiger; Sie sind nur vor Unheil geschützt.
Scott Stanchfield
1740

Java übergibt Argumente immer als Wert , NICHT als Referenz.


Lassen Sie mich dies anhand eines Beispiels erklären :

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 dies in Schritten erklären:

  1. Deklarieren Sie eine Referenz mit dem Namen ftype Foound weisen Sie ihr ein neues Objekt Foomit einem Attribut zu "f".

    Foo f = new Foo("f");

    Geben Sie hier die Bildbeschreibung ein

  2. Auf der Methodenseite wird eine Referenz vom Typ Foomit einem Namen adeklariert und zunächst zugewiesen null.

    public static void changeReference(Foo a)

    Geben Sie hier die Bildbeschreibung ein

  3. Wenn Sie die Methode aufrufen changeReference, wird der Referenz adas Objekt zugewiesen, das als Argument übergeben wird.

    changeReference(f);

    Geben Sie hier die Bildbeschreibung ein

  4. Deklarieren Sie eine Referenz mit dem Namen btype Foound weisen Sie ihr ein neues Objekt Foomit einem Attribut zu "b".

    Foo b = new Foo("b");

    Geben Sie hier die Bildbeschreibung ein

  5. a = bmacht eine neue Zuordnung zu der Referenz a, nicht f , des Objekts , das dessen Attribut ist "b".

    Geben Sie hier die Bildbeschreibung ein

  6. Beim Aufrufen der modifyReference(Foo c)Methode wird eine Referenz cerstellt und dem Objekt ein Attribut zugewiesen "f".

    Geben Sie hier die Bildbeschreibung ein

  7. c.setAttribute("c");ändert das Attribut des Objekts, auf das verwiesen cwird, und es ist dasselbe Objekt, auf das verwiesen fwird.

    Geben Sie hier die Bildbeschreibung ein

Ich hoffe, Sie verstehen jetzt, wie das Übergeben von Objekten als Argumente in Java funktioniert :)

Eng.Fouad
quelle
82
+1 Nettes Zeug. gute Diagramme. Ich habe hier auch eine schöne, prägnante Seite gefunden: adp-gmbh.ch/php/pass_by_reference.html OK Ich gebe zu, dass sie in PHP geschrieben ist, aber es ist das Prinzip, den Unterschied zu verstehen, den ich für wichtig halte (und wie man diesen Unterschied manipuliert) Deine Bedürfnisse).
DaveM
5
@ Eng.Fouad Es ist eine nette Erklärung, aber wenn aauf dasselbe Objekt verweist wie f(und niemals eine eigene Kopie des Objekts darauf fzeigt), sollten alle Änderungen am Objekt, die mit verwendet awerden, ebenfalls fgeändert werden (da beide mit demselben Objekt arbeiten ), muss also irgendwann aeine eigene Kopie der Objektpunkte bekommen f.
0x6C38
14
@MrD Wenn 'a' auf dasselbe Objekt zeigt, auf das auch 'f' zeigt, kann jede Änderung, die über 'a' an diesem Objekt vorgenommen wird, auch über 'f' beobachtet werden, ABER ES hat 'f' NICHT GEÄNDERT. 'f' zeigt immer noch auf dasselbe Objekt. Sie können das Objekt vollständig ändern, aber Sie können niemals ändern, auf was 'f' zeigt. Dies ist das grundlegende Problem, das manche Menschen aus irgendeinem Grund einfach nicht verstehen können.
Mike Braun
@ MikeBraun ... was? Jetzt hast du mich verwirrt: S. Ist das, was Sie gerade geschrieben haben, nicht im Widerspruch zu dem, was 6. und 7. zeigen?
Böse Waschmaschine
6
Dies ist die beste Erklärung, die ich gefunden habe. Was ist übrigens mit der Grundsituation? Für das Argument ist beispielsweise ein int-Typ erforderlich. Übergibt es dennoch die Kopie einer int-Variablen an das Argument?
Allenwang
732

Dies gibt Ihnen einige Einblicke in die Funktionsweise von Java bis zu dem Punkt, dass Sie in Ihrer nächsten Diskussion über Java, das als Referenz oder als Wert übergeben wird, nur lächeln :-)

Schritt eins: Bitte löschen Sie das Wort, das mit 'p' "_ _ _ _ _ _ _" beginnt, aus Ihrem Kopf, insbesondere wenn Sie aus anderen Programmiersprachen stammen. Java und 'p' können nicht im selben Buch, Forum oder sogar txt geschrieben werden.

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

  • Student : Master, bedeutet das, dass Java als Referenz übergeben wird?
  • Meister : Heuschrecke, Nr.

Stellen Sie sich nun vor, was die Referenz / Variable eines Objekts tut / ist:

  1. Eine Variable enthält die Bits, die der JVM mitteilen, wie sie zu dem referenzierten Objekt im Speicher (Heap) gelangt.
  2. Wenn Sie Argumente an eine Methode übergeben, übergeben Sie NICHT die Referenzvariable, sondern eine Kopie der Bits in der Referenzvariablen . So etwas wie das: 3bad086a. 3bad086a stellt einen Weg dar, um zum übergebenen Objekt zu gelangen.
  3. Sie übergeben also nur 3bad086a, dass dies 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 .

Im Folgenden (bitte versuchen Sie nicht, 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.
  • In Zeile 2 wird ein neues Personenobjekt erstellt, das im Speicher gespeichert ist, und die variable Person erhält den Verweis auf das Personenobjekt. Das heißt, seine 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 den Klang der Stille hören
  • Überprüfen Sie den Kommentar in Zeile 5
  • Eine lokale Methodevariable - anotherReferenceToTheSamePersonObject - wird erstellt und dann kommt die Magie in Zeile 6:
    • Die Variable / Bezugsperson ist Bit- für -Bit kopiert und weitergegeben anotherReferenceToTheSamePersonObject innerhalb der Funktion.
    • Es werden keine neuen Instanzen von Person erstellt.
    • Sowohl " person " als auch " anotherReferenceToTheSamePersonObject " haben den gleichen Wert wie 3bad086a.
    • Versuchen Sie dies nicht, aber person == anotherReferenceToTheSamePersonObject wäre wahr.
    • Beide Variablen haben IDENTISCHE KOPIEN der Referenz und beide beziehen sich auf dasselbe Personenobjekt, das gleiche Objekt auf dem Heap und NICHT EINE KOPIE.

Ein Bild sagt mehr als tausend Worte:

Wert übergeben

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

Wenn Sie es nicht verstanden haben, vertrauen Sie mir einfach und denken Sie daran, dass es besser ist zu sagen, dass Java als Wert übergeben wird . Nun, übergeben Sie den Referenzwert . Na ja, noch besser ist das Übergeben einer Kopie des Variablenwerts! ;)

Sie können mich jetzt gerne hassen, aber beachten Sie, dass es angesichts dessen keinen Unterschied zwischen der Übergabe primitiver Datentypen und Objekten gibt, wenn es um Methodenargumente geht.

Sie übergeben immer eine Kopie der Bits des Referenzwerts!

  • 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, die der JVM mitteilt, wie sie zum Objekt gelangt.

Java ist eine Wertübergabe, da Sie innerhalb einer Methode das referenzierte Objekt beliebig ändern können. Unabhängig davon, wie sehr Sie sich bemühen, können Sie die übergebene Variable, die weiterhin referenziert wird, niemals ändern (nicht p _ _ _) _ _ _ _) das gleiche Objekt, egal was!


Die obige Funktion changeName kann den tatsächlichen Inhalt (die Bitwerte) der übergebenen Referenz niemals ändern. Mit anderen Worten, changeName kann die Person nicht dazu bringen, auf ein anderes Objekt zu verweisen.


Natürlich können Sie es kurz machen und einfach sagen, dass Java ein Pass-by-Value ist!

Gevorg
quelle
5
Meinen Sie Zeiger? .. Wenn ich es richtig verstehe, ist in public void foo(Car car){ ... }, carlokal zu foound es enthält den Heap-Speicherort des Objekts? Wenn ich also carden Wert um ändere car = new Car(), zeigt er auf ein anderes Objekt auf dem Heap? und wenn ich carden Eigenschaftswert um ändere car.Color = "Red", wird das Objekt im Heap, auf das gezeigt carwird, geändert. Auch ist es das gleiche in C #? Bitte antworte! Vielen Dank!
dpp
11
@domanokz Du bringst mich um, bitte sag das Wort nicht noch einmal! ;) Beachten Sie, dass ich diese Frage hätte beantworten können, ohne auch 'Referenz' zu sagen. Es ist ein Terminologieproblem und 'p's machen es noch schlimmer. Ich und Scot haben leider unterschiedliche Ansichten dazu. Ich denke, Sie haben verstanden, wie es in Java funktioniert. Jetzt können Sie es als Übergabe nach Wert, nach Objektfreigabe, nach Kopie des Variablenwerts oder als etwas anderes bezeichnen! Es ist mir eigentlich egal, solange du weißt, wie es funktioniert und was in einer Variablen vom Typ Objekt steckt: nur eine Postfachadresse! ;)
Marsellus Wallace
9
Klingt so, als hätten Sie gerade eine Referenz übergeben ? Ich werde mich dafür einsetzen, dass Java immer noch eine kopierte Referenzsprache ist. Die Tatsache, dass es sich um eine kopierte Referenz handelt, ändert nichts an der Terminologie. Beide REFERENZEN zeigen immer noch auf dasselbe Objekt. Dies ist ein puristisches Argument ...
John Strickler
3
Schauen Sie also in der theoretischen Zeile 9 vorbei, System.out.println(person.getName());was wird angezeigt? "Tom" oder "Jerry"? Dies ist das Letzte, was mir helfen wird, diese Verwirrung zu überwinden.
TheBrenny
1
"Der Wert der Referenz " hat es mir schließlich erklärt.
Alexander Shubert
689

Java wird immer von Wert übergeben, ohne Ausnahmen, immer .

Wie kommt es also, dass jeder davon verwirrt sein kann und glaubt, dass Java als Referenz übergeben wird, oder dass er ein Beispiel für Java hat, das als Referenzübergabe fungiert? Der entscheidende Punkt ist , dass Java nie den direkten Zugriff auf die Werte liefert Objekte selbst , in allen Umständen. Der einzige Zugriff auf Objekte erfolgt über einen Verweis auf dieses Objekt. Da auf Java-Objekte immer über eine Referenz und nicht direkt zugegriffen wird, werden Felder und Variablen sowie Methodenargumente häufig als Objekte bezeichnet , wenn sie pedantisch nur Verweise auf Objekte sind .Die Verwirrung ergibt sich aus dieser (genau genommen falschen) Änderung der Nomenklatur.

Also, wenn Sie eine Methode aufrufen

  • Für primitive Argumente ( int, long, etc.), ist der Durchgang durch den Wert der Ist - Wert des Primitiv (beispielsweise 3).
  • Bei Objekten ist der Wert für die Übergabe der Wert der Referenz auf das Objekt .

Wenn Sie also doSomething(foo)und public void doSomething(Foo foo) { .. }die beiden Foos Referenzen kopiert haben , die auf dieselben Objekte verweisen.

Das Übergeben eines Verweises auf ein Objekt als Wert ähnelt natürlich dem Übergeben eines Objekts als Verweis (und ist in der Praxis nicht davon zu unterscheiden).

SCdF
quelle
7
Da die Werte der Grundelemente unveränderlich sind (wie String), ist der Unterschied zwischen den beiden Fällen nicht wirklich relevant.
Paŭlo Ebermann
4
Genau. Nach allem, was Sie anhand des beobachtbaren JVM-Verhaltens erkennen können, können Grundelemente als Referenz übergeben werden und auf dem Heap leben. Sie tun es nicht, aber das ist in keiner Weise zu beobachten.
Schwerkraft
6
Primitive sind unveränderlich? ist das neu in Java 7?
user85421
4
Zeiger sind unveränderlich, Grundelemente sind im Allgemeinen veränderlich. String ist auch kein Grundelement, sondern ein Objekt. Darüber hinaus ist die zugrunde liegende Struktur des Strings ein veränderbares Array. Das einzig Unveränderliche daran ist die Länge, die die inhärente Natur von Arrays ist.
kingfrito_5005
3
Dies ist eine weitere Antwort, die auf die weitgehend semantische Natur des Arguments hinweist. Die in dieser Antwort angegebene Referenzdefinition würde Java zum "Pass-by-Reference" machen. Der Autor gibt im letzten Absatz im Wesentlichen zu, dass er "in der Praxis nicht von" Pass-by-Reference "zu unterscheiden ist. Ich bezweifle, dass OP nach dem Wunsch fragt, die Java-Implementierung zu verstehen, sondern zu verstehen, wie Java richtig verwendet wird. Wenn es in der Praxis nicht zu unterscheiden wäre, wäre es sinnlos, sich darum zu kümmern, und selbst darüber nachzudenken wäre Zeitverschwendung.
Loduwijk
331

Java übergibt Referenzen nach Wert.

Sie können also die Referenz, die übergeben wird, nicht ändern.

ScArcher2
quelle
27
Aber die Sache, die immer wieder wiederholt wird "Sie können den Wert von Objekten, die in Argumenten übergeben werden, nicht ändern", ist eindeutig falsch. Möglicherweise können Sie sie nicht dazu bringen, auf ein anderes Objekt zu verweisen, aber Sie können ihren Inhalt ändern, indem Sie ihre Methoden aufrufen. IMO bedeutet dies, dass Sie alle Vorteile von Referenzen verlieren und keine zusätzlichen Garantien erhalten.
Timmmm
34
Ich habe nie gesagt, dass Sie den Wert von Objekten, die in Argumenten übergeben werden, nicht ändern können. Ich werde sagen "Sie können den Wert der als Methodenargument übergebenen Objektreferenz nicht ändern", was eine wahre Aussage über die Java-Sprache ist. Natürlich können Sie den Status des Objekts ändern (solange es nicht unveränderlich ist).
ScArcher2
20
Beachten Sie, dass Sie in Java keine Objekte übergeben können. Die Objekte bleiben auf dem Haufen. Zeiger auf die Objekte können übergeben werden (die für die aufgerufene Methode auf den Stapelrahmen kopiert werden). Sie ändern also niemals den übergebenen Wert (den Zeiger), aber Sie können ihm folgen und das Ding auf dem Heap ändern, auf das er zeigt. Das ist Pass-by-Value.
Scott Stanchfield
10
Klingt so, als hätten Sie gerade eine Referenz übergeben ? Ich werde mich dafür einsetzen, dass Java immer noch eine kopierte Referenzsprache ist. Die Tatsache, dass es sich um eine kopierte Referenz handelt, ändert nichts an der Terminologie. Beide REFERENZEN zeigen immer noch auf dasselbe Objekt. Dies ist ein puristisches Argument ...
John Strickler
3
Java übergibt kein Objekt, sondern den Wert des Zeigers an das Objekt. Dadurch wird ein neuer Zeiger auf diesen Speicherort des ursprünglichen Objekts in einer neuen Variablen erstellt. Wenn Sie den Wert (die Speicheradresse, auf die er zeigt) dieser Zeigervariablen in einer Methode ändern, bleibt der im Methodenaufrufer verwendete ursprüngliche Zeiger unverändert. Wenn Sie den Parameter als Referenz bezeichnen, bedeutet die Tatsache, dass es sich um eine Kopie der Originalreferenz handelt, nicht um die Originalreferenz selbst, sodass jetzt zwei Verweise auf das Objekt vorhanden sind, dass es als Wert übergeben wird
theferrit32
238

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

Wenn Sie sagen, "Java ist ein Pass-by-Whatever (Referenz / Wert)", 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.

Crashkurs auf Stack / Heap, bevor wir zur Java-Implementierung kommen: Die Werte werden auf eine schöne, geordnete Weise auf und ab des Stacks verschoben, wie ein Stapel Teller in einer Cafeteria. Der Speicher im Heap (auch als dynamischer Speicher bezeichnet) ist zufällig und unorganisiert. Die JVM findet nur Speicherplatz, wo immer sie kann, und gibt ihn frei, da die Variablen, die ihn verwenden, nicht mehr benötigt werden.

Okay. Zunächst werden lokale Grundelemente auf den Stapel gelegt. Also dieser Code:

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

führt dazu:

Grundelemente auf dem Stapel

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 als Zeiger bezeichnen, aber einige Java-Entwickler sind gegen das Wort "Zeiger". Wie auch immer. Sie müssen nur wissen, dass die Adresse des Objekts auf dem Stapel liegt.

Wie so:

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

ab * 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-Speicherplatz, und die Adresse jedes Objekts befindet sich im Array.

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

Marx Brüder

Was wird also übergeben, wenn Sie eine Methode aufrufen? Wenn Sie ein Objekt übergeben, übergeben Sie tatsächlich die Adresse des Objekts. Einige sagen vielleicht den "Wert" der Adresse, andere sagen, es ist nur ein Verweis auf das Objekt. Dies ist die Entstehung des heiligen Krieges zwischen "Referenz" - und "Wert" -Vertretern. Was Sie es nennen, ist nicht so wichtig, als dass Sie verstehen, dass die Adresse des Objekts ü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);
}

Ein String wird erstellt und Speicherplatz dafür wird im Heap zugewiesen, und die Adresse des Strings wird auf dem Stapel gespeichert und mit dem Bezeichner versehen hisName, da die Adresse des zweiten Strings mit der des ersten übereinstimmt, kein neuer String erstellt wird und Es wird kein neuer Heap-Speicherplatz zugewiesen, aber ein neuer Bezeichner wird auf dem Stapel erstellt. Dann rufen wir auf shout(): Ein neuer Stapelrahmen wird erstellt und ein neuer Bezeichner namewird erstellt und die Adresse des bereits vorhandenen Strings zugewiesen.

la da di da da da da

Also, Wert, Referenz? Sie sagen "Kartoffel".

cutmancometh
quelle
7
Sie sollten jedoch ein komplexeres Beispiel folgen, in dem eine Funktion eine Variable zu ändern scheint, auf deren Adresse sie eine Referenz hat.
Brian Peterson
34
Die Leute "tanzen nicht um das eigentliche Problem" von Stapel gegen Haufen, weil das nicht das eigentliche Problem ist. Es ist bestenfalls ein Implementierungsdetail und im schlimmsten Fall geradezu falsch. (Es ist durchaus möglich, dass Objekte auf dem Stapel leben. Google "Escape-Analyse". Und eine große Anzahl von Objekten enthält Grundelemente, die wahrscheinlich nicht auf dem Stapel leben.) Das eigentliche Problem ist genau der Unterschied zwischen Referenztypen und Werttypen - insbesondere, dass der Wert einer Variablen vom Referenztyp eine Referenz ist, nicht das Objekt, auf das sie verweist.
CHao
8
Es ist ein "Implementierungsdetail", da Java niemals erforderlich ist, um Ihnen tatsächlich zu zeigen, wo sich ein Objekt im Speicher befindet, und tatsächlich entschlossen zu sein scheint, das Weitergeben dieser Informationen zu vermeiden . Es könnte das Objekt auf den Stapel legen, und Sie würden es nie erfahren. Wenn Sie sich interessieren, konzentrieren Sie sich auf das Falsche - und in diesem Fall bedeutet dies, das eigentliche Problem zu ignorieren.
CHao
10
Und so oder so ist "Primitive gehen auf den Stapel" falsch. Primitive lokale Variablen werden auf den Stapel gelegt. (Wenn sie nicht entfernt wurden, natürlich.) Aber auch lokale Referenzvariablen . Und primitive Elemente, die in einem Objekt definiert sind, leben überall dort, wo das Objekt lebt.
CHao
9
Stimmen Sie den Kommentaren hier zu. Stapel / Haufen ist ein Nebenproblem und nicht relevant. Einige Variablen befinden sich möglicherweise auf dem Stapel, andere befinden sich im statischen Speicher (statische Variablen) und viele befinden sich auf dem Heap (alle Objektmitgliedsvariablen). KEINE dieser Variablen kann als Referenz übergeben werden: Mit einer aufgerufenen Methode ist es NIEMALS möglich, den Wert einer Variablen zu ändern, die als Argument übergeben wird. Daher gibt es in Java keine Referenzübergabe.
Fisch am
195

Vergleichen Sie die folgenden C ++ - und Java- Snippets , um den Kontrast zu zeigen :

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

In Java gibt es nur zwei Arten der Übergabe: nach Wert für integrierte Typen und nach Wert des Zeigers für Objekttypen.

Eclipse
quelle
7
+1 Ich würde auch Dog **objPtrPtrdem C ++ - Beispiel hinzufügen , auf diese Weise können wir ändern, auf was der Zeiger "zeigt".
Amro
1
Dies beantwortet jedoch nicht die angegebene Frage in Java. Tatsächlich ist alles in Javas Methode Pass By Value, nichts weiter.
Tauitdnmd
2
Dieses Beispiel beweist nur, dass Java das Äquivalent des Zeigers in C-Nr. Wo Pass-by-Werte in C grundsätzlich schreibgeschützt sind? Am Ende ist dieser ganze Thread unverständlich
amdev
179

Java übergibt Verweise auf Objekte nach Wert.

John Channing
quelle
Das ist die beste Antwort. Kurz und richtig.
Woens
166

Grundsätzlich wirkt sich die Neuzuweisung 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);
}

auszudrucken wird "Hah!"statt null. Der Grund dafür ist, dass bares sich um eine Kopie des Werts von handelt baz, auf die nur verwiesen wird "Hah!". Wenn es die tatsächliche Referenz selbst wäre, dann foohätte neu definiert bazzu null.

Hank Gay
quelle
8
Ich würde eher sagen, dass bar eine Kopie des Referenz-Baz (oder Baz-Alias) ist, der anfänglich auf dasselbe Objekt verweist.
MaxZoom
Gibt es keinen kleinen Unterschied zwischen der String-Klasse und allen anderen Klassen?
Mehdi Karamosly
152

Ich kann nicht glauben, dass noch niemand Barbara Liskov erwähnt hat. Als sie 1974 die CLU entwarf, stieß sie auf dasselbe Terminologieproblem und erfand den Begriff Call by Sharing (auch als Call by Object-Sharing und Call by Object bezeichnet ) für diesen speziellen Fall von "Call by Value", bei dem sich der Wert befindet eine Referenz".

Jörg W Mittag
quelle
3
Ich mag diese Unterscheidung in der Nomenklatur. Es ist bedauerlich, dass Java Call by Sharing für Objekte unterstützt, aber nicht Call by Value (wie C ++). Java unterstützt Call-by-Value nur für primitive Datentypen und nicht für zusammengesetzte Datentypen.
Derek Mahar
2
Ich glaube wirklich nicht, dass wir einen zusätzlichen Begriff brauchten - es ist einfach ein Wert für einen bestimmten Werttyp. Würde das Hinzufügen von "Aufruf durch Primitiv" eine Klarstellung hinzufügen?
Scott Stanchfield
8
Anruf durch Teilen
wulfgarpro
Ich kann also eine Referenz übergeben, indem ich ein globales Kontextobjekt freigebe oder sogar ein Kontextobjekt übergebe, das andere Referenzen enthält. Ich pass-by-Wert immer noch, aber zumindest habe ich Zugang zu Referenzen Ich kann sonst sie auf etwas ändern und machen.
YoYo
1
@ Sanjeev: Call-by-Object-Sharing ist ein Sonderfall der Wertübergabe. Viele Leute argumentieren jedoch vehement, dass Java (und ähnliche Sprachen wie Python, Ruby, ECMAScript, Smalltalk) als Referenz dienen. Ich würde es auch lieber als Pass-by-Value bezeichnen, aber Call-by-Object-Sharing scheint ein vernünftiger Begriff zu sein, dem auch Leute zustimmen können, die argumentieren, dass es sich nicht um Pass-by-Value handelt.
Jörg W Mittag
119

Der Kern der Sache ist, dass die Wortreferenz im Ausdruck "Referenzübergabe" etwas völlig anderes bedeutet als die übliche Bedeutung der Wortreferenz in Java.

Normalerweise bedeutet in Java Referenz eine Referenz auf ein Objekt . Die technischen Begriffe, die als Referenz / Wert aus der Programmiersprachtheorie verwendet werden, sprechen jedoch von einer Referenz auf die Speicherzelle, in der sich die Variable befindet , was etwas völlig anderes ist.

JacquesB
quelle
7
@Gevorg - Was ist dann eine "NullPointerException"?
Hot Licks
5
@Hot: Eine leider genannte Ausnahme von vor Java hat sich auf eine klare Terminologie festgelegt. Die semantisch äquivalente Ausnahme in c # heißt NullReferenceException.
JacquesB
4
Es schien mir immer, dass die Verwendung von "Referenz" in der Java-Terminologie eine Beeinträchtigung ist, die das Verständnis behindert.
Hot Licks
Ich habe gelernt, diese sogenannten "Zeiger auf Objekte" als "Handle auf das Objekt" zu bezeichnen. Dies reduzierte die Mehrdeutigkeit.
Moronkreacionz
86

In Java ist alles eine Referenz. Wenn Sie Point pnt1 = new Point(0,0);also Folgendes haben: Java führt Folgendes aus:

  1. Erstellt ein neues Punktobjekt
  2. Erstellt eine neue Punktreferenz und initialisiert diese Referenz auf einen Punkt (siehe) auf einem zuvor erstellten Punktobjekt.
  3. Von hier aus können Sie über die Lebensdauer des Punktobjekts über die pnt1-Referenz auf dieses Objekt zugreifen. Wir können also sagen, dass Sie in Java das Objekt durch seine Referenz manipulieren.

Geben Sie hier die Bildbeschreibung ein

Java übergibt keine Methodenargumente als Referenz. es übergibt sie nach 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);

Erstellen von zwei verschiedenen Punktobjekten mit zwei verschiedenen zugeordneten Referenzen. Geben Sie hier die Bildbeschreibung ein

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

In dieser Zeile geht 'Pass-by-Value' ins Spiel ...

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

Referenzen pnt1und pnt2werden von Wert übergeben , um die schwierige Methode, was bedeutet , dass nun bei Ihnen Referenzen pnt1und pnt2haben ihre copiesNamen arg1und arg2.So pnt1und arg1 Punkte auf das gleiche Objekt. (Gleiches gilt für pnt2und arg2) Geben Sie hier die Bildbeschreibung ein

In der trickyMethode:

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

Geben Sie hier die Bildbeschreibung ein

Weiter in der trickyMethode

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

Hier legen Sie zunächst neue erstellen tempPunktreferenz , die wird Punkt auf derselben Stelle wie arg1Referenz. Dann verschieben Sie die Referenz arg1, um auf dieselbe Stelle wie die arg2Referenz zu zeigen. Schließlich arg2wird darauf wie an der gleichen Stelle temp.

Geben Sie hier die Bildbeschreibung ein

Von hier Umfang trickyVerfahren gegangen ist , und Sie haben keinen Zugang mehr zu den Referenzen: arg1, arg2, temp. Wichtig ist jedoch, dass alles, was Sie mit diesen Referenzen tun, wenn sie sich im Leben befinden, das Objekt, auf das sie verweisen , dauerhaft beeinflusst .

trickyWenn Sie also nach dem Ausführen der Methode zu zurückkehren main, haben Sie folgende Situation: Geben Sie hier die Bildbeschreibung ein

Die vollständige Ausführung des Programms erfolgt nun wie folgt:

X1: 0         Y1: 0
X2: 0         Y2: 0
X1: 100       Y1: 100
X2: 0         Y2: 0
Srle
quelle
7
Wenn Sie in Java sagen "In Java ist alles Referenz", meinen Sie, dass alle Objekte als Referenz übergeben werden. Primitive Datentypen werden nicht als Referenz übergeben.
Eric
Ich kann den getauschten Wert in der Hauptmethode drucken. Fügen Sie in der Trickey-Methode die folgende Anweisung hinzu arg1.x = 1; arg1.y = 1; arg2.x = 2; arg2.y = 2;, so dass arg1 jetzt die pnt2-Aktualisierung und arg2 jetzt die pnt1-Referenz enthält, also der DruckX1: 2 Y1: 2 X2: 1 Y2: 1
Shahid Ghafoor
85

Java wird immer als Wert übergeben, nicht als Referenz

Zunächst müssen wir verstehen, was Wertübergabe und Referenzübergabe sind.

Wert übergeben bedeutet, dass Sie eine Kopie des übergebenen Werts des Aktualparameters im Speicher erstellen. Dies ist eine Kopie des Inhalts des Aktualparameters .

Referenzübergabe (auch als Adressübergabe bezeichnet) bedeutet, dass eine Kopie der Adresse des Aktualparameters gespeichert wird .

Manchmal kann Java die Illusion vermitteln, als Referenz zu gelten. Lassen Sie uns anhand des folgenden Beispiels sehen, 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

Lassen Sie uns Schritt für Schritt verstehen:

Test t = new Test();

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

erste Illustration

new PassByValue().changeValue(t);

Wenn die Referenz t an die Funktion übergeben wird, wird der tatsächliche Referenzwert des Objekttests nicht direkt übergeben, sondern eine Kopie von t erstellt und anschließend an die Funktion übergeben. Da es als Wert übergeben wird , wird eine Kopie der Variablen und nicht die tatsächliche Referenz übergeben. Da wir sagten, der Wert von t sei 0x100234, haben sowohl t als auch f den gleichen Wert und daher zeigen sie auf das gleiche Objekt.

zweite Abbildung

Wenn Sie etwas in der Funktion unter Verwendung von Referenz f ändern, wird der vorhandene Inhalt des Objekts geändert. Deshalb haben wir die Ausgabe erhalten changevalue, die in der Funktion aktualisiert wird.

Um dies besser 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 das ein werfen NullPointerException? Nein, da nur eine Kopie der Referenz übergeben wird. Im Falle einer Referenzübergabe hätte es ein NullPointerException, wie unten gezeigt, werfen können :

dritte Abbildung

Hoffentlich hilft das.

Ganesh
quelle
71

Eine Referenz ist immer ein Wert, wenn sie dargestellt wird, unabhängig davon, welche Sprache Sie verwenden.

Schauen wir uns Assembly oder eine Speicherverwaltung auf niedriger Ebene an. Auf CPU-Ebene wird ein Verweis auf etwas sofort zu einem Wert, wenn er 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 dieser Stelle gibt es einen Wert (Byte, Wort, was auch immer). In Montage haben wir eine praktische Lösung , die eine geben , Namen zu bestimmtem Ort (auch bekannt als Variable), aber wenn Sie den Code kompiliert, ersetzt der Assembler einfach Namen mit der festgelegten Stelle genau wie Ihr Browser ersetzt Domain - Namen mit IP - Adressen.

Bis ins Mark ist es technisch unmöglich, einen Verweis auf irgendetwas in irgendeiner Sprache zu übergeben, ohne es darzustellen (wenn es sofort zu einem Wert wird).

Nehmen wir an, wir haben eine Variable Foo, ihre Position befindet sich im 47. Byte im Speicher und ihr Wert ist 5. Wir haben eine weitere Variable Ref2Foo, die sich im 223. Byte im Speicher befindet, und ihr Wert wird 47. Dieser Ref2Foo könnte eine technische Variable sein , nicht explizit vom Programm erstellt. Wenn Sie nur 5 und 47 ohne weitere Informationen betrachten, sehen Sie nur zwei Werte . Wenn Sie sie als Referenz verwenden, müssen 5wir reisen , um zu erreichen :

(Name)[Location] -> [Value at the Location]
---------------------
(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 abhängig von der Sprache und den verschiedenen Methodenaufrufmodi einige Möglichkeiten, die Variable an die Methode zu übergeben :

  1. 5 wird in eines der CPU-Register (dh EAX) kopiert.
  2. 5 bringt PUSHd auf den Stapel.
  3. 47 wird in eines der CPU-Register kopiert
  4. 47 Zum Stapel drücken.
  5. 223 wird in eines der CPU-Register kopiert.
  6. 223 bringt PUSHd auf den Stapel.

In allen Fällen, in denen ein Wert - eine Kopie eines vorhandenen Werts - erstellt wurde, liegt es nun an der Empfangsmethode, diesen zu verarbeiten. Wenn Sie "Foo" in die Methode schreiben, wird diese entweder aus EAX ausgelesen oder automatisch dereferenziert oder doppelt dereferenziert. Der Vorgang hängt davon ab, wie die Sprache funktioniert und / oder was der Foo-Typ vorschreibt. Dies ist dem Entwickler verborgen, bis er den Dereferenzierungsprozess umgeht. Eine Referenz ist also ein Wert, wenn sie dargestellt wird, da eine Referenz ein Wert ist, der verarbeitet werden muss (auf Sprachebene).

Jetzt haben wir Foo an die Methode übergeben:

  • In Fall 1. und 2. Foo = 9wirkt sich das Ändern von Foo ( ) nur auf den lokalen Bereich aus, da Sie über eine Kopie des Werts verfügen. Innerhalb der Methode können wir nicht einmal feststellen, wo sich der ursprüngliche Foo im Speicher befand.
  • In Fall 3. und 4. Wenn Sie Standard-Sprachkonstrukte verwenden und Foo ( Foo = 11) ändern , kann dies Foo global ändern (abhängig von der Sprache, dh Java oder wie Pascals procedure findMin(x, y, z: integer;var m: integer); ). Wenn jedoch die Sprache können Sie den dereferenzieren Prozess umgehen, die Sie ändern können 47, sagen 49. Zu diesem Zeitpunkt scheint Foo geändert worden zu sein, wenn Sie es gelesen haben, da Sie den lokalen Zeiger darauf geändert haben . Und wenn Sie dieses Foo innerhalb der Methode ( Foo = 12) ändern, werden Sie wahrscheinlich die Ausführung des Programms FUBAR (auch bekannt als Segfault), da Sie in einen anderen Speicher als erwartet schreiben. Sie können sogar einen Bereich ändern, der ausführbare Dateien enthalten soll Programm und Schreiben darauf ändern den laufenden Code (Foo ist jetzt nicht bei 47). ABER Foo's Wert von47hat sich nicht global geändert, nur die innerhalb der Methode, da 47es sich auch um eine Kopie der Methode handelte.
  • In Fall 5. und 6. Wenn Sie 223innerhalb der Methode Änderungen vornehmen, entsteht das gleiche Chaos wie in 3. oder 4. (ein Zeiger, der auf einen jetzt schlechten Wert zeigt, der wiederum als Zeiger verwendet wird), aber dies ist immer noch ein lokaler Problem, da 223 kopiert wurde . Wenn Sie jedoch in der Lage sind, den angegebenen Wert zu dereferenzieren Ref2Foo( dh 223), zu erreichen und zu ändern 47, z. B. zu 49, wirkt sich dies global auf Foo aus , da in diesem Fall die Methoden eine Kopie erhalten haben 223 , die referenzierte Methode jedoch 47nur einmal vorhanden ist, und diese ändern to 49führt jede Ref2Foodoppelte Dereferenzierung zu einem falschen Wert.

Wenn Sie nur unwichtige Details auswählen, geben selbst Sprachen, die als Referenz übergeben werden, Werte an Funktionen weiter. Diese Funktionen wissen jedoch, dass sie diese für Dereferenzierungszwecke verwenden müssen. Diese Übergabe der Referenz als Wert ist dem Programmierer nur verborgen, da sie praktisch unbrauchbar ist und die Terminologie nur als Referenz übergeben wird .

Eine strikte Übergabe nach Wert ist ebenfalls nutzlos. Dies würde bedeuten, dass jedes Mal, wenn wir eine Methode mit dem Array als Argument aufrufen, ein 100-MByte-Array kopiert werden muss. Daher kann Java nicht strikt nach Wert übergeben werden. Jede Sprache würde einen Verweis auf dieses riesige Array (als Wert) übergeben und entweder einen Copy-on-Write-Mechanismus verwenden, wenn dieses Array lokal innerhalb der Methode geändert werden kann, oder es der Methode (wie Java) ermöglicht, das Array global (von) zu ändern Die Ansicht des Anrufers) und einige Sprachen ermöglichen es, den Wert der Referenz selbst zu ändern.

Kurz gesagt und in Javas eigener Terminologie ist Java ein Pass-by-Wert, bei dem Wert sein kann: entweder ein realer Wert oder ein Wert , der eine Referenz darstellt .

Karatedog
quelle
1
In einer Sprache mit Referenzübergabe ist das, was übergeben wird (die Referenz), vergänglich; Der Empfänger soll es nicht "kopieren". In Java ist das Übergeben eines Arrays eine "Objektkennung" - entspricht einem Zettel mit der Aufschrift "Objekt Nr. 24601", als das erstellte 24601. Objekt ein Array war. Der Empfänger kann "Objekt Nr. 24601" an eine beliebige Stelle kopieren, und jeder mit einem Zettel mit der Aufschrift "Objekt Nr. 24601" kann mit einem der Array-Elemente alles tun, was er möchte. Das Muster der übergebenen Bits würde natürlich nicht "Objekt # 24601" sagen, aber ...
Supercat
... der entscheidende Punkt ist, dass der Empfänger der Objekt-ID, die das Array identifiziert, diese Objekt-ID speichern kann, wo immer er will, und sie an jeden weitergeben kann, und jeder Empfänger jederzeit auf das Array zugreifen oder es ändern kann will. Im Gegensatz dazu könnte die aufgerufene Methode, wenn ein Array als Referenz in einer Sprache wie Pascal übergeben würde, die solche Dinge unterstützt, mit dem Array tun, was sie wollte, aber die Referenz nicht so speichern, dass Code das Array ändern kann nachdem es zurückgekehrt war.
Supercat
Tatsächlich in Pascal Sie können die Adresse jeder Variablen erhalten, sei es bestanden per Referenz oder lokal kopiert, addrtut nicht typisierten es, @tut es mit Art, und Sie können die referenzierte Variable später ändern (lokal kopiert diejenigen ausgenommen) bekannt . Aber ich verstehe nicht, warum Sie das tun würden. Dieser Zettel in Ihrem Beispiel (Objekt Nr. 24601) ist eine Referenz. Er dient dazu, das Array im Speicher zu finden. Er enthält keine Array-Daten an sich. Wenn Sie Ihr Programm neu starten, erhält dasselbe Array möglicherweise eine andere Objekt-ID, auch wenn der Inhalt derselbe ist wie im vorherigen Lauf.
Karatedog
Ich hatte gedacht, der Operator "@" sei nicht Teil von Standard Pascal, sondern wurde als allgemeine Erweiterung implementiert. Ist es Teil des Standards? Mein Punkt war, dass in einer Sprache mit echtem Pass-by-Ref und ohne die Fähigkeit, einen nicht kurzlebigen Zeiger auf ein kurzlebiges Objekt zu erstellen, Code, der den einzigen Verweis auf ein Array irgendwo im Universum enthält, bevor das Array übergeben wird Die Referenz kann wissen, dass der Empfänger, sofern er nicht "betrügt", danach immer noch die einzige Referenz enthält. Der einzig sichere Weg, dies in Java zu erreichen, wäre das
Erstellen
... das ein kapselt AtomicReferenceund weder die Referenz noch sein Ziel verfügbar macht, sondern Methoden enthält, um Dinge mit dem Ziel zu tun; Sobald der Code, an den das Objekt übergeben wurde, zurückgegeben wird, sollte der AtomicReference[auf den sein Ersteller einen direkten Verweis geführt hat] ungültig gemacht und aufgegeben werden. Das würde die richtige Semantik liefern, aber es wäre langsam und schwierig.
Supercat
70

Java ist ein Aufruf nach Wert

Wie es funktioniert

  • Sie übergeben immer eine Kopie der Bits des Referenzwerts!

  • Wenn es sich um einen primitiven Datentyp handelt, enthalten diese Bits den Wert des primitiven Datentyps selbst. Wenn wir also den Wert des Headers innerhalb der Methode ändern, werden die Änderungen außerhalb nicht berücksichtigt.

  • Wenn es sich um einen Objektdatentyp wie Foo foo = new Foo () handelt, wird in diesem Fall eine Kopie der Adresse des Objekts wie eine Dateiverknüpfung übergeben. Nehmen wir an, wir haben eine Textdatei abc.txt unter C: \ desktop und nehmen eine Verknüpfung von an die gleiche Datei und legen Sie diese in C: \ desktop \ abc-Verknüpfung. Wenn Sie also über C: \ desktop \ abc.txt auf die Datei zugreifen und ' Stapelüberlauf ' schreiben und die Datei schließen, öffnen Sie die Datei erneut über die Verknüpfung write "ist die größte Online-Community, die Programmierer lernen können", dann lautet die vollständige Dateiänderung "Stack Overflow ist die größte Online-Community, die Programmierer lernen können".Das heißt, es spielt keine Rolle, von wo aus Sie die Datei öffnen. Jedes Mal, wenn wir auf dieselbe Datei zugreifen, können wir hier Foo als Datei annehmen und annehmen, dass foo bei 123hd7h gespeichert ist (ursprüngliche Adresse wie C: \ desktop \ abc.txt ). Adresse und 234jdid (kopierte Adresse wie C: \ desktop \ abc-Verknüpfung, die tatsächlich die ursprüngliche Adresse der darin enthaltenen Datei enthält). Machen Sie zum besseren Verständnis eine Verknüpfungsdatei und fühlen Sie sich.

Himanshu Arora
quelle
66

Es gibt bereits gute Antworten, die dies abdecken. Ich wollte einen kleinen Beitrag leisten, indem ich ein sehr einfaches Beispiel (das kompiliert wird) mit den Verhaltensweisen zwischen Pass-by-Reference in c ++ und Pass-by-Value in Java teile.

Ein paar Punkte:

  1. Der Begriff "Referenz" ist überladen mit zwei getrennten Bedeutungen. In Java bedeutet es einfach einen Zeiger, aber im Kontext von "Pass-by-Reference" bedeutet es ein Handle für die ursprüngliche Variable, die übergeben wurde.
  2. Java ist Pass-by-Value . Java ist ein Nachkomme von C (unter anderem). Vor C unterstützten mehrere (aber nicht alle) frühere Sprachen wie FORTRAN und COBOL PBR, C jedoch nicht. PBR erlaubte diesen anderen Sprachen, Änderungen an den übergebenen Variablen innerhalb von Unterroutinen vorzunehmen. Um dasselbe zu erreichen (dh die Werte von Variablen innerhalb von Funktionen zu ändern), haben C-Programmierer Zeiger auf Variablen in Funktionen übergeben. Von C inspirierte Sprachen wie Java haben diese Idee übernommen und übergeben weiterhin Zeiger auf Methoden wie C, außer dass Java seine Zeiger References aufruft. Auch dies ist eine andere Verwendung des Wortes "Referenz" als in "Pass-By-Reference".
  3. C ++ ermöglicht die Referenzübergabe durch Deklarieren eines Referenzparameters mit dem Zeichen "&" (das zufällig dasselbe Zeichen ist, mit dem sowohl in C als auch in C ++ "die Adresse einer Variablen" angegeben wird). Wenn wir beispielsweise einen Zeiger als Referenz übergeben, zeigen der Parameter und das Argument nicht nur auf dasselbe Objekt. Sie sind vielmehr dieselbe Variable. Wenn einer auf eine andere Adresse oder auf null gesetzt wird, tut dies auch der andere.
  4. Im folgenden C ++ - Beispiel übergebe ich einen Zeiger auf eine nullterminierte Zeichenfolge als Referenz . Und im folgenden Java-Beispiel übergebe ich einen Java-Verweis auf einen String (wieder dasselbe wie ein Zeiger auf einen String) nach Wert. Beachten Sie die Ausgabe in den Kommentaren.

C ++ Pass als Referenzbeispiel:

using namespace std;
#include <iostream>

void change (char *&str){   // the '&' makes this a reference parameter
    str = NULL;
}

int main()
{
    char *str = "not Null";
    change(str);
    cout<<"str is " << str;      // ==>str is <null>
}

Java übergeben "eine Java-Referenz" als Wertbeispiel

public class ValueDemo{

    public void change (String str){
        str = null;
    }

     public static void main(String []args){
        ValueDemo vd = new ValueDemo();
        String str = "not null";
        vd.change(str);
        System.out.println("str is " + str);    // ==> str is not null!!
                                                // Note that if "str" was
                                                // passed-by-reference, it
                                                // WOULD BE NULL after the
                                                // call to change().
     }
}

BEARBEITEN

Einige Leute haben Kommentare geschrieben, die darauf hinweisen, dass sie entweder meine Beispiele nicht betrachten oder das C ++ - Beispiel nicht erhalten. Ich bin mir nicht sicher, wo sich die Trennung befindet, aber das Erraten des c ++ - Beispiels ist nicht klar. Ich poste das gleiche Beispiel in Pascal, weil ich denke, dass Pass-by-Reference in Pascal sauberer aussieht, aber ich könnte mich irren. Ich könnte die Leute mehr verwirren; Ich hoffe nicht.

In Pascal werden als Referenz übergebene Parameter als "var-Parameter" bezeichnet. Beachten Sie in der folgenden Prozedur setToNil das Schlüsselwort 'var' vor dem Parameter 'ptr'. Wenn ein Zeiger an diese Prozedur übergeben wird, wird er als Referenz übergeben . Beachten Sie das Verhalten: Wenn diese Prozedur ptr auf nil setzt (das ist pascal speak for NULL), wird das Argument auf nil gesetzt - das können Sie in Java nicht tun.

program passByRefDemo;
type 
   iptr = ^integer;
var
   ptr: iptr;

   procedure setToNil(var ptr : iptr);
   begin
       ptr := nil;
   end;

begin
   new(ptr);
   ptr^ := 10;
   setToNil(ptr);
   if (ptr = nil) then
       writeln('ptr seems to be nil');     { ptr should be nil, so this line will run. }
end.

BEARBEITEN 2

Einige Auszüge aus "THE Java Programming Language" von Ken Arnold, James Gosling (dem Erfinder von Java) und David Holmes, Kapitel 2, Abschnitt 2.6.5

Alle Parameter an Methoden werden "nach Wert" übergeben . Mit anderen Worten, Werte von Parametervariablen in einer Methode sind Kopien des als Argumente angegebenen Aufrufers.

Er fährt fort, den gleichen Punkt in Bezug auf Objekte zu machen. . .

Sie sollten beachten, dass, wenn der Parameter eine Objektreferenz ist, die Objektreferenz - nicht das Objekt selbst - "nach Wert" übergeben wird .

Und gegen Ende desselben Abschnitts macht er eine breitere Aussage darüber, dass Java nur als Wert und niemals als Referenz gilt.

Die Java-Programmiersprache übergibt Objekte nicht als Referenz. Es übergibt Objektreferenzen nach Wert . Da sich zwei Kopien derselben Referenz auf dasselbe tatsächliche Objekt beziehen, sind Änderungen, die über eine Referenzvariable vorgenommen wurden, über die andere sichtbar. Es gibt genau einen Parameterübergabemodus - Übergabe nach Wert - und dies hilft, die Dinge einfach zu halten.

Dieser Abschnitt des Buches enthält eine ausführliche Erläuterung der Parameterübergabe in Java sowie der Unterscheidung zwischen Referenzübergabe und Wertübergabe und wird vom Ersteller von Java erstellt. Ich würde jeden ermutigen, es zu lesen, besonders wenn Sie immer noch nicht überzeugt sind.

Ich denke, der Unterschied zwischen den beiden Modellen ist sehr subtil. Wenn Sie nicht dort programmiert haben, wo Sie tatsächlich Pass-by-Reference verwendet haben, ist es leicht zu übersehen, wo sich zwei Modelle unterscheiden.

Ich hoffe, dass dies die Debatte regelt, aber wahrscheinlich nicht.

BEARBEITEN 3

Ich könnte ein wenig besessen von diesem Beitrag sein. Wahrscheinlich, weil ich das Gefühl habe, dass die Hersteller von Java versehentlich Fehlinformationen verbreitet haben. Wenn sie anstelle des Wortes "Referenz" für Zeiger etwas anderes verwendet hätten, beispielsweise Dingleberry, hätte es kein Problem gegeben. Man könnte sagen, "Java übergibt Dingleberries nach Wert und nicht nach Referenz", und niemand wäre verwirrt.

Dies ist der Grund, warum nur Java-Entwickler Probleme damit haben. Sie schauen sich das Wort "Referenz" an und glauben genau zu wissen, was das bedeutet, also machen sie sich nicht einmal die Mühe, das gegnerische Argument zu berücksichtigen.

Wie auch immer, ich habe in einem älteren Beitrag einen Kommentar bemerkt, der eine Ballon-Analogie ergab, die mir sehr gut gefallen hat. So sehr, dass ich mich entschied, einige Cliparts zusammenzukleben, um eine Reihe von Cartoons zu erstellen, um den Punkt zu veranschaulichen.

Übergeben einer Referenz als Wert - Änderungen an der Referenz werden nicht im Bereich des Aufrufers angezeigt, die Änderungen am Objekt jedoch. Dies liegt daran, dass die Referenz kopiert wird, aber sowohl das Original als auch die Kopie auf dasselbe Objekt verweisen. Objektreferenzen nach Wert übergeben

Referenzübergabe - Es gibt keine Kopie der Referenz. Eine einzelne Referenz wird sowohl vom Aufrufer als auch von der aufgerufenen Funktion gemeinsam genutzt. Alle Änderungen an der Referenz oder den Daten des Objekts werden im Bereich des Anrufers berücksichtigt. Als Referenz übergeben

BEARBEITEN 4

Ich habe Beiträge zu diesem Thema gesehen, die die Implementierung der Parameterübergabe auf niedriger Ebene in Java beschreiben. Ich finde das großartig und sehr hilfreich, weil es eine abstrakte Idee konkretisiert. Für mich geht es jedoch eher um das in der Sprachspezifikation beschriebene Verhalten als um die technische Umsetzung des Verhaltens. Dies ist ein Auszug aus der Java-Sprachspezifikation, Abschnitt 8.4.1 :

Wenn die Methode oder der Konstruktor aufgerufen wird (§15.12), initialisieren die Werte der tatsächlichen Argumentausdrücke neu erstellte Parametervariablen vom deklarierten Typ, bevor der Hauptteil der Methode oder des Konstruktors ausgeführt wird. Der in der DeclaratorId angezeigte Bezeichner kann als einfacher Name im Hauptteil der Methode oder des Konstruktors verwendet werden, um auf den formalen Parameter zu verweisen.

Das heißt, Java erstellt eine Kopie der übergebenen Parameter, bevor eine Methode ausgeführt wird. Wie die meisten Leute, die im College Compiler studiert haben, habe ich "The Dragon Book" verwendet, das DAS Compiler-Buch ist. Es enthält eine gute Beschreibung von "Call-by-Value" und "Call-by-Reference" in Kapitel 1. Die Call-by-Value-Beschreibung stimmt genau mit den Java-Spezifikationen überein.

Als ich in den 90er Jahren Compiler studierte, verwendete ich die erste Ausgabe des Buches von 1986, die Java um 9 oder 10 Jahre älter war. Ich bin jedoch gerade auf eine Kopie der 2. Ausgabe von 2007 gestoßen, in der tatsächlich Java erwähnt wird! Abschnitt 1.6.6 mit der Bezeichnung "Parameterübergabemechanismen" beschreibt die Parameterübergabe ziemlich gut. Hier ist ein Auszug unter der Überschrift "Call-by-Value", in dem Java erwähnt wird:

Beim Call-by-Value wird der Aktualparameter ausgewertet (wenn es sich um einen Ausdruck handelt) oder kopiert (wenn es sich um eine Variable handelt). Der Wert wird an der Stelle platziert, die zu dem entsprechenden formalen Parameter der aufgerufenen Prozedur gehört. Diese Methode wird in C und Java verwendet und ist eine häufige Option in C ++ sowie in den meisten anderen Sprachen.

Sanjeev
quelle
4
Ehrlich gesagt können Sie diese Antwort vereinfachen, indem Sie sagen, dass Java nur für primitive Typen als Wert übergeben wird. Alles, was von Object erbt, wird effektiv als Referenz übergeben, wobei die Referenz der Zeiger ist, den Sie übergeben.
Scuba Steve
1
@ JuanMendes, ich habe gerade C ++ als Beispiel verwendet; Ich könnte Ihnen Beispiele in anderen Sprachen geben. Der Begriff "Referenzübergabe" existierte lange bevor C ++ existierte. Es ist ein Lehrbuchbegriff mit einer sehr spezifischen Definition. Und per Definition wird Java nicht als Referenz übergeben. Sie können den Begriff auf diese Weise weiter verwenden, wenn Sie möchten, aber Ihre Verwendung stimmt nicht mit der Lehrbuchdefinition überein. Es ist nicht nur ein Zeiger auf einen Zeiger. Es ist ein Konstrukt, das von der Sprache bereitgestellt wird, um "Pass By Reference" zu ermöglichen. Bitte schauen Sie sich mein Beispiel genau an, aber nehmen Sie bitte nicht mein Wort und schauen Sie selbst nach.
Sanjeev
2
@AutomatedMike Ich denke, dass die Beschreibung von Java als "Pass-by-Reference" ebenfalls irreführend ist. Ihre Referenz ist nichts weiter als ein Zeiger. Wie Sanjeev Wert schrieb, sind Referenz und Zeiger Lehrbuchbegriffe, die ihre eigene Bedeutung haben, unabhängig davon, was Java-Ersteller verwenden.
Jędrzej Dudkiewicz
1
@ArtanisZeratul der Punkt ist subtil, aber nicht kompliziert. Bitte schauen Sie sich den Cartoon an, den ich gepostet habe. Ich fand es ziemlich einfach zu folgen. Dass Java Pass-by-Value ist, ist nicht nur eine Meinung. Es ist wahr durch Lehrbuch Definition von Pass-by-Wert. Zu den "Leuten, die immer sagen, dass es sich um einen Wert handelt" gehören Informatiker wie James Gosling, der Schöpfer von Java. Bitte beachten Sie die Zitate aus seinem Buch unter "Edit 2" in meinem Beitrag.
Sanjeev
1
Was für eine großartige Antwort, "Pass by Reference" gibt es in Java (und anderen Sprachen wie JS) nicht.
neuer Ordner
56

Soweit ich weiß, kennt Java Call nur nach Wert. Dies bedeutet, dass Sie für primitive Datentypen mit einer Kopie und für Objekte mit einer Kopie des Verweises auf die Objekte arbeiten. Ich denke jedoch, dass es einige Fallstricke gibt; Dies funktioniert beispielsweise nicht:

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

Dadurch wird Hello World und nicht World Hello ausgefüllt, da Sie in der Swap-Funktion Kopien verwenden, die keinen Einfluss auf die Referenzen im Main haben. Wenn Ihre Objekte jedoch nicht unveränderlich sind, können Sie sie beispielsweise ändern:

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

Dadurch wird Hello World in der Befehlszeile ausgefüllt. Wenn Sie StringBuffer in String ändern, wird nur Hello erzeugt, da String unveränderlich ist. Zum Beispiel:

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

Sie könnten jedoch einen Wrapper für String wie diesen erstellen, der es ermöglicht, ihn mit Strings zu verwenden:

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: Ich glaube, dies ist auch der Grund, StringBuffer zu verwenden, wenn es darum geht, zwei Strings hinzuzufügen, da Sie das ursprüngliche Objekt ändern können, was Sie mit unveränderlichen Objekten wie String nicht können.

Kukudas
quelle
+1 für den Swap-Test - wahrscheinlich die einfachste und zuordenbarste Methode, um zwischen Referenzübergabe und Referenzübergabe nach Wert zu unterscheiden . Wenn Sie einfach eine Funktion schreiben können swap(a, b), die (1) austauscht aund baus der POV des Aufrufers (2) typunabhängig ist, soweit die statische Typisierung dies zulässt (dh die Verwendung mit einem anderen Typ erfordert nichts weiter als das Ändern der deklarierten Typen von aund b). und (3) erfordert nicht, dass der Aufrufer einen Zeiger oder Namen explizit übergibt, dann unterstützt die Sprache die Übergabe als Referenz.
CHao
"..für primitive Datentypen arbeiten Sie mit einer Kopie und für Objekte arbeiten Sie mit einer Kopie des Verweises auf die Objekte" - perfekt geschrieben!
Raúl
55

Nein, es wird nicht als Referenz übergeben.

Java wird gemäß der Java-Sprachspezifikation als Wert übergeben:

Wenn die Methode oder der Konstruktor aufgerufen wird (§15.12), initialisieren die Werte der tatsächlichen Argumentausdrücke neu erstellte Parametervariablen , jeweils vom deklarierten Typ, bevor der Hauptteil der Methode oder des Konstruktors ausgeführt wird. Der in der DeclaratorId angezeigte Bezeichner kann als einfacher Name im Hauptteil der Methode oder des Konstruktors verwendet werden, um auf den formalen Parameter zu verweisen .

rorrohprog
quelle
52

Lassen Sie mich versuchen, mein Verständnis anhand von vier Beispielen zu erklären. Java ist ein Pass-by-Value und kein Pass-by-Reference

/ **

Wert übergeben

In Java werden alle Parameter als Wert übergeben, dh das Zuweisen eines Methodenarguments ist für den Aufrufer nicht sichtbar.

* /

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

Ergebnis

output : output
value : Nikhil
valueflag : false

Beispiel 2:

/ ** * * Wert übergeben * * /

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

Ergebnis

output : output
value : Nikhil
valueflag : false

Beispiel 3:

/ ** Dieser 'Pass By Value' hat das Gefühl 'Pass By Reference'

Einige Leute sagen, dass primitive Typen und 'String' 'Wert übergeben' und Objekte 'Referenz übergeben' sind.

Anhand dieses Beispiels können wir jedoch verstehen, dass es sich nur um einen tatsächlichen Wert handelt, wobei zu berücksichtigen ist, dass wir hier die Referenz als Wert übergeben. dh: Referenz wird als Wert übergeben. Das ist der Grund, warum sie sich ändern können und dies auch nach dem lokalen Geltungsbereich gilt. Wir können die tatsächliche Referenz jedoch nicht außerhalb des ursprünglichen Bereichs ändern. Was dies bedeutet, zeigt das nächste Beispiel für 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";
    }
}

Ergebnis

output : output
student : Student [id=10, name=Anand]

Beispiel 4:

/ **

Zusätzlich zu dem, was in Beispiel 3 (PassByValueObjectCase1.java) erwähnt wurde, können wir die tatsächliche Referenz nicht außerhalb des ursprünglichen Bereichs ändern. "

Hinweis: Ich füge den Code für nicht ein private class Student. Die Klassendefinition für Studentist dieselbe wie in Beispiel 3.

* /

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

}

Ergebnis

output : output
student : Student [id=10, name=Nikhil]
Spiderman
quelle
49

In Java können Sie niemals als Referenz übergeben. Eine der offensichtlichen Möglichkeiten besteht darin, dass Sie mehr als einen Wert aus einem Methodenaufruf zurückgeben möchten. Betrachten Sie das folgende Codebit in C ++:

void getValues(int& arg1, int& arg2) {
    arg1 = 1;
    arg2 = 2;
}
void caller() {
    int x;
    int y;
    getValues(x, y);
    cout << "Result: " << x << " " << y << endl;
}

Manchmal möchten Sie dasselbe Muster in Java verwenden, können dies jedoch nicht. Zumindest nicht direkt. Stattdessen könnten Sie so etwas tun:

void getValues(int[] arg1, int[] arg2) {
    arg1[0] = 1;
    arg2[0] = 2;
}
void caller() {
    int[] x = new int[1];
    int[] y = new int[1];
    getValues(x, y);
    System.out.println("Result: " + x[0] + " " + y[0]);
}

Wie in früheren Antworten erläutert, übergeben Sie in Java einen Zeiger auf das Array als Wert in getValues. Dies ist ausreichend, da die Methode dann das Array-Element ändert und Sie gemäß Konvention erwarten, dass Element 0 den Rückgabewert enthält. Natürlich können Sie dies auch auf andere Weise tun, z. B. indem Sie Ihren Code so strukturieren, dass dies nicht erforderlich ist, oder eine Klasse erstellen, die den Rückgabewert enthalten oder dessen Festlegung zulassen kann. Das einfache Muster, das Ihnen in C ++ oben zur Verfügung steht, ist in Java jedoch nicht verfügbar.

Jared Oberhaus
quelle
48

Ich dachte, ich würde diese Antwort beitragen, um weitere Details aus den Spezifikationen hinzuzufügen.

Erstens, Was ist der Unterschied zwischen durch Bezugnahme vorbei Wert vs. vorbei?

Übergabe als Referenz bedeutet, dass der Parameter der aufgerufenen Funktionen mit dem übergebenen Argument des Aufrufers identisch ist (nicht der Wert, sondern die Identität - die Variable selbst).

Wert übergeben bedeutet, dass der Parameter der aufgerufenen Funktionen eine Kopie des übergebenen Arguments des Aufrufers ist.

Oder aus Wikipedia zum Thema Pass-by-Reference

Bei der Call-by-Reference-Auswertung (auch als Pass-by-Reference bezeichnet) erhält eine Funktion einen impliziten Verweis auf eine als Argument verwendete Variable anstelle einer Kopie ihres Werts. Dies bedeutet normalerweise, dass die Funktion die als Argument verwendete Variable ändern (dh zuweisen) kann - etwas, das von ihrem Aufrufer gesehen wird.

Und zum Thema Pass-by-Value

Beim Call-by-Value wird der Argumentausdruck ausgewertet und der resultierende Wert an die entsprechende Variable in der Funktion [...] gebunden. Wenn die Funktion oder Prozedur ihren Parametern Werte zuweisen kann, wird nur ihre lokale Kopie zugewiesen [...].

Zweitens müssen wir wissen, was Java in seinen Methodenaufrufen verwendet. In der Java-Sprachspezifikation heißt es

Wenn die Methode oder der Konstruktor aufgerufen wird (§15.12), initialisieren die Werte der tatsächlichen Argumentausdrücke neu erstellte Parametervariablen , jeweils vom deklarierten Typ, bevor der Hauptteil der Methode oder des Konstruktors ausgeführt wird.

Daher weist es den Wert des Arguments der entsprechenden Parametervariablen zu (oder bindet ihn).

Was ist der Wert des Arguments?

Betrachten wir Referenztypen, die Java Virtual Machine Specification Staaten

Es gibt drei Arten von Referenztypen : Klassentypen, Array-Typen und Schnittstellentypen. Ihre Werte sind Verweise auf dynamisch erstellte Klasseninstanzen, Arrays oder Klasseninstanzen oder Arrays, die Schnittstellen implementieren.

Die Java-Sprachspezifikation gibt auch an

Die Referenzwerte (oft nur Referenzen) sind Zeiger auf diese Objekte und eine spezielle Nullreferenz, die sich auf kein Objekt bezieht.

Der Wert eines Arguments (eines Referenztyps) ist ein Zeiger auf ein Objekt. Beachten Sie, dass eine Variable, ein Aufruf einer Methode mit einem Rückgabetyp für Referenztypen und ein Ausdruck zur Instanzerstellung ( new ...) alle in einen Referenztypwert aufgelöst werden.

Damit

public void method (String param) {}
...
String var = new String("ref");
method(var);
method(var.toString());
method(new String("ref"));

Alle binden den Wert einer Referenz auf eine StringInstanz an den neu erstellten Parameter der Methode param. Genau das beschreibt die Definition von Pass-by-Value. Als solches ist Java ein Pass-by-Value .

Die Tatsache, dass Sie der Referenz folgen können, um eine Methode aufzurufen oder auf ein Feld des referenzierten Objekts zuzugreifen, ist für die Konversation völlig irrelevant. Die Definition von Pass-by-Reference war

Dies bedeutet normalerweise, dass die Funktion die als Argument verwendete Variable ändern (dh zuweisen) kann - etwas, das von ihrem Aufrufer gesehen wird.

In Java bedeutet das Ändern der Variablen das Neuzuweisen. Wenn Sie in Java die Variable innerhalb der Methode neu zuweisen, bleibt sie für den Aufrufer unbemerkt. Das Ändern des Objekts, auf das die Variable verweist, ist ein völlig anderes Konzept.


Primitive Werte sind auch in der Java Virtual Machine Specification definiert, hier . Der Wert des Typs ist der entsprechende Integral- oder Gleitkommawert, der entsprechend codiert ist (8, 16, 32, 64 usw. Bits).

Sotirios Delimanolis
quelle
42

In Java werden nur Referenzen übergeben und als Wert übergeben:

Java-Argumente werden alle als Wert übergeben (die Referenz wird kopiert, wenn sie von der Methode verwendet wird):

Bei primitiven Typen ist das Java-Verhalten einfach: Der Wert wird in eine andere Instanz des primitiven Typs kopiert.

Bei Objekten ist dies dasselbe: Objektvariablen sind Zeiger (Buckets), die nur die Objektadresse enthalten , die mit dem Schlüsselwort "new" erstellt wurde, und werden wie primitive Typen kopiert.

Das Verhalten kann anders aussehen als bei primitiven Typen: Da die kopierte Objektvariable dieselbe Adresse enthält (an dasselbe Objekt). Der Inhalt / die Mitglieder des Objekts können innerhalb einer Methode noch geändert werden und später nach außen zugreifen, wodurch die Illusion entsteht, dass das (enthaltende) Objekt selbst als Referenz übergeben wurde.

"String" -Objekte scheinen ein gutes Gegenbeispiel zur städtischen Legende zu sein, die besagt, dass "Objekte als Referenz übergeben werden":

Tatsächlich können Sie mit einer Methode niemals den Wert eines als Argument übergebenen Strings aktualisieren:

Ein String-Objekt enthält Zeichen eines als final deklarierten Arrays , das nicht geändert werden kann. Nur die Adresse des Objekts kann mit "new" durch eine andere ersetzt werden. Wenn Sie "new" zum Aktualisieren der Variablen verwenden, kann nicht von außen auf das Objekt zugegriffen werden, da die Variable ursprünglich als Wert übergeben und kopiert wurde.

user1767316
quelle
Also ist es byRef in Bezug auf Objekte und byVal in Bezug auf Primitive?
Mox
@mox bitte lesen: Objekte werden nicht als Referenz übergeben, dies ist ein Ledgend: String a = neuer String ("unverändert");
user1767316
1
@Aaron "Referenz übergeben" bedeutet nicht "Wert übergeben, der Mitglied eines Typs ist, der in Java als" Referenz "bezeichnet wird". Die beiden Verwendungen von "Referenz" bedeuten unterschiedliche Dinge.
philipxy
2
Ziemlich verwirrende Antwort. Der Grund, warum Sie ein Zeichenfolgenobjekt in einer Methode, die als Referenz übergeben wird, nicht ändern können, besteht darin, dass Zeichenfolgenobjekte vom Design her unveränderlich sind und Sie keine Aktionen ausführen können strParam.setChar ( i, newValue ). Das heißt, Strings werden wie alles andere als Wert übergeben. Da String ein nicht primitiver Typ ist, ist dieser Wert ein Verweis auf das Objekt, das mit new erstellt wurde, und Sie können dies mithilfe von String.intern () überprüfen. .
Zakmck
1
Stattdessen können Sie eine Zeichenfolge nicht über param = "eine andere Zeichenfolge" (entspricht einer neuen Zeichenfolge ("eine andere Zeichenfolge")) ändern, da der Referenzwert von param (der jetzt auf "eine andere Zeichenfolge" verweist) nicht von der Methode zurückkommen kann Körper. Dies gilt jedoch für jedes andere Objekt. Der Unterschied besteht darin, dass Sie, wenn die Klassenschnittstelle dies zulässt, param.changeMe () ausführen können und sich das Objekt der obersten Ebene, auf das verwiesen wird, ändert, da param trotz des Referenzwerts von param selbst darauf zeigt (der adressierte Ort in C-Begriffen) kann nicht von der Methode zurück angezeigt werden.
Zakmck
39

Der Unterschied oder vielleicht nur die Art und Weise, wie ich mich erinnere, als ich den gleichen Eindruck hatte wie das Originalplakat, ist folgender: Java wird immer als Wert übergeben. Alle Objekte (in Java alles außer Primitiven) in Java sind Referenzen. Diese Referenzen werden als Wert übergeben.

shsteimer
quelle
2
Ich finde Ihren vorletzten Satz sehr irreführend. Es ist nicht wahr, dass "alle Objekte in Java Referenzen sind". Es sind nur die Verweise auf diese Objekte, die Verweise sind.
Dawood ibn Kareem
37

Wie viele Leute bereits erwähnt haben, ist Java immer ein Pass-by-Value

Hier ist ein weiteres Beispiel, das Ihnen hilft, den Unterschied zu verstehen ( das klassische Swap-Beispiel ):

public class Test {
  public static void main(String[] args) {
    Integer a = new Integer(2);
    Integer b = new Integer(3);
    System.out.println("Before: a = " + a + ", b = " + b);
    swap(a,b);
    System.out.println("After: a = " + a + ", b = " + b);
  }

  public static swap(Integer iA, Integer iB) {
    Integer tmp = iA;
    iA = iB;
    iB = tmp;
  }
}

Drucke:

Vorher: a = 2, b = 3
Nachher: ​​a = 2, b = 3

Dies liegt daran, dass iA und iB neue lokale Referenzvariablen sind, die denselben Wert wie die übergebenen Referenzen haben (sie zeigen auf a bzw. b). Der Versuch, die Referenzen von iA oder iB zu ändern, ändert sich also nur im lokalen Bereich und nicht außerhalb dieser Methode.

pek
quelle
32

Java hat nur einen Wert übergeben. Ein sehr einfaches Beispiel, um dies zu validieren.

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();
}
Gaurav
quelle
4
Dies ist der klarste und einfachste Weg, um zu sehen, dass Java als Wert übergeben wird. Der Wert von obj( null) wurde an übergeben init, kein Verweis auf obj.
David Schwartz
31

Ich denke immer an "Pass by Copy". Es ist eine Kopie des Wertes, sei es primitiv oder als Referenz. Wenn es ein Grundelement ist, ist es eine Kopie der Bits, die der Wert sind, und wenn es ein Objekt ist, ist es eine Kopie der Referenz.

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

Ausgabe von Java PassByCopy:

Name = Maxx
Name = Fido

Primitive Wrapper-Klassen und Strings sind unveränderlich, sodass ein Beispiel mit diesen Typen nicht wie andere Typen / Objekte funktioniert.

SWD
quelle
5
"Pass by Copy" bedeutet "Pass-by-Value " .
TJ Crowder
@TJCrowder. "Pass by Copy" ist möglicherweise eine geeignetere Methode, um dasselbe Konzept für Java-Zwecke auszudrücken: Semantisch ist es sehr schwierig, eine Idee zu entwickeln, die der Übergabe als Referenz ähnelt, wie Sie es in Java wollen.
Mike Nagetier
@mikerodent - Entschuldigung, dem bin ich nicht gefolgt. :-) "Pass-by-Value" und "Pass-by-Reference" sind die richtigen Kunstbegriffe für diese Konzepte. Wir sollten sie verwenden (ihre Definitionen bereitstellen, wenn Menschen sie brauchen), anstatt neue zu erfinden. Oben habe ich gesagt, "Kopie übergeben" ist das, was "Wert übergeben" bedeutet, aber tatsächlich ist nicht klar, was "Kopie übergeben" bedeutet - eine Kopie von was? Der Wert? (ZB Objektreferenz.) Das Objekt selbst? Also halte ich mich an die Begriffe der Kunst. :-)
TJ Crowder
28

Im Gegensatz zu einigen anderen Sprachen können Sie in Java nicht zwischen Wertübergabe und Referenzübergabe wählen. Alle Argumente werden als Wert übergeben. Ein Methodenaufruf kann zwei Arten von Werten an eine Methode übergeben: Kopien primitiver Werte (z. B. Werte von int und double) und Kopien von Verweisen auf Objekte.

Wenn eine Methode einen Parameter vom primitiven Typ ändert, haben Änderungen am Parameter keine Auswirkung auf den ursprünglichen Argumentwert in der aufrufenden Methode.

Wenn es um Objekte geht, können Objekte selbst nicht an Methoden übergeben werden. Also übergeben wir die Referenz (Adresse) des Objekts. Mit dieser Referenz können wir das Originalobjekt bearbeiten.

So erstellt und speichert Java Objekte: Wenn wir ein Objekt erstellen, speichern wir die Adresse des Objekts in einer Referenzvariablen. Lassen Sie uns die folgende Aussage analysieren.

Account account1 = new Account();

"Konto Konto1" ist der Typ und Name der Referenzvariablen, "=" ist der Zuweisungsoperator, "Neu" fragt nach dem erforderlichen Speicherplatz vom System. Der Konstruktor rechts vom Schlüsselwort new, der das Objekt erstellt, wird implizit durch das Schlüsselwort new aufgerufen. Die Adresse des erstellten Objekts (Ergebnis des rechten Werts, bei dem es sich um einen Ausdruck handelt, der als "Ausdruck zur Erstellung von Klasseninstanzen" bezeichnet wird) wird dem linken Wert (bei dem es sich um eine Referenzvariable mit einem Namen und einem angegebenen Typ handelt) mithilfe des Zuweisungsoperators zugewiesen.

Obwohl die Referenz eines Objekts als Wert übergeben wird, kann eine Methode dennoch mit dem referenzierten Objekt interagieren, indem sie ihre öffentlichen Methoden unter Verwendung der Kopie der Objektreferenz aufruft. Da die im Parameter gespeicherte Referenz eine Kopie der Referenz ist, die als Argument übergeben wurde, beziehen sich der Parameter in der aufgerufenen Methode und das Argument in der aufrufenden Methode auf dasselbe Objekt im Speicher.

Das Übergeben von Verweisen auf Arrays anstelle der Array-Objekte selbst ist aus Leistungsgründen sinnvoll. Da alles in Java als Wert übergeben wird, wird bei Übergabe von Array-Objekten eine Kopie jedes Elements übergeben. Bei großen Arrays würde dies Zeit verschwenden und erheblichen Speicherplatz für die Kopien der Elemente verbrauchen.

In der Abbildung unten sehen Sie, dass wir in der Hauptmethode zwei Referenzvariablen haben (diese werden in C / C ++ als Zeiger bezeichnet, und ich denke, dieser Begriff erleichtert das Verständnis dieser Funktion.). Primitive und Referenzvariablen werden im Stapelspeicher gespeichert (linke Seite in den Bildern unten). Array1- und Array2-Referenzvariablen "Punkt" (wie C / C ++ - Programmierer es nennen) oder Referenz auf a- und b-Arrays, die Objekte (Werte, die diese Referenzvariablen enthalten, sind Adressen von Objekten) im Heapspeicher sind (rechte Seite in den Bildern unten) .

Beispiel 1 übergeben

Wenn wir den Wert der Referenzvariablen array1 als Argument an die reverseArray-Methode übergeben, wird in der Methode eine Referenzvariable erstellt, und diese Referenzvariable zeigt auf dasselbe Array (a).

public class Test
{
    public static void reverseArray(int[] array1)
    {
        // ...
    }

    public static void main(String[] args)
    {
        int[] array1 = { 1, 10, -7 };
        int[] array2 = { 5, -190, 0 };

        reverseArray(array1);
    }
}

Beispiel 2 übergeben

Also, wenn wir sagen

array1[0] = 5;

Bei der reverseArray-Methode wird das Array a geändert.

Wir haben eine andere Referenzvariable in der reverseArray-Methode (Array2), die auf ein Array zeigt. C. Wenn wir sagen würden

array1 = array2;

Bei der reverseArray-Methode zeigt die Referenzvariable array1 in der Methode reverseArray nicht mehr auf Array a und zeigt auf Array c (gepunktete Linie im zweiten Bild).

Wenn wir den Wert der Referenzvariablen array2 als Rückgabewert der Methode reverseArray zurückgeben und diesen Wert der Referenzvariablen array1 in der Hauptmethode zuweisen, zeigt Array1 in main auf Array c.

Schreiben wir also alle Dinge, die wir jetzt auf einmal getan haben.

public class Test
{
    public static int[] reverseArray(int[] array1)
    {
        int[] array2 = { -7, 0, -1 };

        array1[0] = 5; // array a becomes 5, 10, -7

        array1 = array2; /* array1 of reverseArray starts
          pointing to c instead of a (not shown in image below) */
        return array2;
    }

    public static void main(String[] args)
    {
        int[] array1 = { 1, 10, -7 };
        int[] array2 = { 5, -190, 0 };

        array1 = reverseArray(array1); /* array1 of 
         main starts pointing to c instead of a */
    }
}

Geben Sie hier die Bildbeschreibung ein

Und jetzt, da die reverseArray-Methode beendet ist, sind ihre Referenzvariablen (Array1 und Array2) verschwunden. Das heißt, wir haben jetzt nur die beiden Referenzvariablen in der Hauptmethode Array1 und Array2, die auf die Arrays c bzw. b zeigen. Keine Referenzvariable zeigt auf ein Objekt (Array) a. Es ist also für die Speicherbereinigung geeignet.

Sie können Array1 auch den Wert von array2 in main zuweisen. Array1 würde auf b zeigen.

Michael
quelle
27

Ich habe einen Thread für diese Art von Fragen für gewidmet erstellt beliebige Programmiersprachen hier .

Java wird ebenfalls erwähnt . Hier ist die kurze Zusammenfassung:

  • Java übergibt die Parameter als Wert
  • "by value" ist die einzige Möglichkeit in Java, einen Parameter an eine Methode zu übergeben
  • Durch die Verwendung von Methoden aus dem als Parameter angegebenen Objekt wird das Objekt geändert, da die Referenzen auf die ursprünglichen Objekte verweisen. (wenn diese Methode selbst einige Werte ändert)
sven
quelle
27

Um es kurz zu machen, Java- Objekte haben einige sehr eigenartige Eigenschaften.

Im Allgemeinen hat Java primitive Typen ( int, bool, char, doubleusw.) , die direkt von Wert übergeben werden. Dann hat Java Objekte (alles, was davon herrührt java.lang.Object). Objekte werden eigentlich immer über eine Referenz behandelt (eine Referenz ist ein Zeiger, den Sie nicht berühren können). Das bedeutet, dass Objekte tatsächlich als Referenz übergeben werden, da die Referenzen normalerweise nicht interessant sind. Dies bedeutet jedoch, dass Sie nicht ändern können, auf welches Objekt verwiesen wird, da die Referenz selbst als Wert übergeben wird.

Klingt das seltsam und verwirrend? Betrachten wir, wie C-Implementierungen als Referenz und als Wert übergeben werden. In C lautet die Standardkonvention "Wert übergeben". void foo(int x)Übergibt ein int als Wert. void foo(int *x)ist eine Funktion, die kein int a, sondern einen Zeiger auf ein int will : foo(&a). Man würde dies mit dem &Operator verwenden, um eine variable Adresse zu übergeben.

Nehmen Sie dies zu C ++, und wir haben Referenzen. Referenzen sind im Grunde genommen (in diesem Zusammenhang) syntaktischer Zucker, der den Zeigerteil der Gleichung verbirgt: void foo(int &x)wird von aufgerufen foo(a), wobei der Compiler selbst weiß, dass es sich um eine Referenz handelt und die Adresse der Nichtreferenz übergeben awerden sollte. In Java sind alle Variablen, die sich auf Objekte beziehen, tatsächlich vom Referenztyp, wodurch für die meisten Zwecke und Zwecke ein Aufruf durch Referenz erzwungen wird, ohne dass die fein abgestimmte Steuerung (und Komplexität) beispielsweise von C ++ bereitgestellt wird.

Paul de Vrieze
quelle
Ich denke, es kommt meinem Verständnis des Java-Objekts und seiner Referenz sehr nahe. Das Objekt in Java wird von einer Referenzkopie (oder einem Alias) an eine Methode übergeben.
MaxZoom