7750 Stimmen

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

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

Was ist die Erklärung?

6 Stimmen

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

6 Stimmen

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

73 Stimmen

Java übergibt die Referenz nach Wert.

63voto

kukudas Punkte 4716

Soweit ich weiß, kennt Java nur den Aufruf nach Wert. Das bedeutet, dass Sie für primitive Datentypen mit einer Kopie arbeiten und für Objekte mit einer Kopie des Verweises auf die Objekte. Ich denke jedoch, dass es einige Fallstricke gibt; zum Beispiel funktioniert dies 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("Hallo");
    StringBuffer s2 = new StringBuffer("Welt");
    swap(s1, s2);
    System.out.println(s1);
    System.out.println(s2);
}

Dies füllt Hallo Welt und nicht Welt Hallo, weil im Swap-Funktion, den Sie Kopien verwenden, die keinen Einfluss auf die Referenzen im Hauptteil haben. Aber wenn Ihre Objekte nicht unveränderlich sind, können Sie es ändern, zum Beispiel so:

public static void appendWorld(StringBuffer s1) {
    s1.append(" Welt");
}

public static void main(String[] args) {
    StringBuffer s = new StringBuffer("Hallo");
    appendWorld(s);
    System.out.println(s);
}

Dies füllt Hallo Welt auf der Befehlszeile. Wenn Sie StringBuffer in String ändern, wird es nur Hallo produzieren, weil String unveränderlich ist. Zum Beispiel:

public static void appendWorld(String s){
    s = s+" Welt";
}

public static void main(String[] args) {
    String s = new String("Hallo");
    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 +" Welt";
}

public static void main(String[] args) {
    StringWrapper s = new StringWrapper("Hallo");
    appendWorld(s);
    System.out.println(s.value);
}

Bearbeitung: Ich glaube, dies ist auch der Grund, warum man StringBuffer verwendet, wenn es um das "Hinzufügen" von zwei Strings geht, weil Sie das Originalobjekt ändern können, was Sie mit unveränderlichen Objekten wie String nicht können.

61voto

Herupkhart Punkte 499

Nein, es handelt sich nicht um eine Verweisübertragung.

Laut der Java-Sprachspezifikation wird Java nach Wert übertragen:

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 Körper der Methode oder des Konstruktors ausgeführt wird. Der Bezeichner, der im DeclaratorId erscheint, kann im Körper der Methode oder des Konstruktors als einfacher Name verwendet werden, um auf den formalen Parameter zu verweisen.

0 Stimmen

Java hat sich wie folgt definiert. In der Geschichte der Informatik existierten die Konzepte und Modi der Datenübergabe an Funktionen lange bevor Kernighan & Ritchie die Verwirrung von Zeigern und Werten erfanden. Für Java kann man feststellen, dass der eigene Dogmatismus, OBJEKT ORIENTIERT zu sein, gebrochen wird, wenn im Kontext von Aufrufen plötzlich eine Referenz ein Wert ist, anstatt der Objektinstanz.

57voto

spiderman Punkte 10559

Lassen Sie mich versuchen, mein Verständnis mit Hilfe von vier Beispielen zu erklären. Java ist durch Wertübertragung und nicht durch Referenzübertragung gekennzeichnet

/**

Wertübertragung

In Java werden alle Parameter durch Wert übertragen, d.h. die Zuweisung 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' ist in diesem Beispiel unwichtig. Wir sind eher an
         * 'value' und 'valueflag' interessiert
         */
        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übertragung * */

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' ist in diesem Beispiel unwichtig. Wir sind eher an
         * 'value' und 'valueflag' interessiert
         */
        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:

/** Diese 'Wertübertragung hat ein Gefühl von 'Referenzübertragung'

Einige Leute sagen, dass primitive Typen und 'String' 'wertübertragen' sind und Objekte 'referenzübertragen' sind.

Aber aus diesem Beispiel können wir verstehen, dass es tatsächlich nur Wertübertragung ist, unter Berücksichtigung, dass wir hier die Referenz als Wert übergeben. d.h. die Referenz wird durch Wert übergeben. Deshalb können wir ändern und es bleibt auch nach dem lokalen Scope gültig. Aber wir können die tatsächliche Referenz außerhalb des ursprünglichen Scopes nicht ändern. Was das bedeutet, wird durch das nächste Beispiel von PassByValueObjectCase2 demonstriert.

*/

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' ist in diesem Beispiel unwichtig. Wir sind eher an
         * 'student' interessiert
         */
        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 erwähnt wurde (PassByValueObjectCase1.java), können wir die tatsächliche Referenz außerhalb des ursprünglichen Scopes nicht ändern."

Hinweis: Ich kopiere den Code für private class Student nicht. Die Klassendefinition für Student ist gleich wie in Beispiel 3.

*/

public class PassByValueObjectCase2 {

    public static void main(String[] args) {
        new PassByValueObjectCase2().caller();
    }

    public void caller() {
        // student hat die tatsächliche Referenz auf ein erstelltes Student-Objekt
        // können wir diese tatsächliche Referenz außerhalb des lokalen Scopes ändern? Mal sehen
        Student student = new Student(10, "Nikhil");
        String output = method(student);
        /*
         * 'output' ist in diesem Beispiel unwichtig. Wir sind eher an
         * 'student' interessiert
         */
        System.out.println("output : " + output);
        System.out.println("student : " + student); // Wird es Nikhil oder Anand drucken?
    }

    public String method(Student student) {
        student = new Student(20, "Anand");
        return "output";
    }

}

Ergebnis

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

57voto

Sotirios Delimanolis Punkte 262318

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

Zuerst, Was ist der Unterschied zwischen dem Übergeben von Referenz vs. dem Übergeben von Wert?

Das Übergeben per Referenz bedeutet, dass der Parameter der aufgerufenen Funktion identisch mit dem vom Aufrufer übergebenen Argument ist (nicht der Wert, sondern die Identität

  • die Variable selbst).

Das Übergeben per Wert bedeutet, dass der Parameter der aufgerufenen Funktion eine Kopie des vom Aufrufer übergebenen Arguments sein wird.

Oder aus Wikipedia, zum Thema Übergabe per Referenz

Bei der Auswertung per Referenz (auch als Übergabe per Referenz bezeichnet) erhält eine Funktion eine implizite Referenz auf eine Variable, die als Argument verwendet wird, anstatt eine Kopie ihres Werts. Dies bedeutet typischerweise, dass die Funktion die Variable, die als Argument verwendet wird, ändern kann (z.B. zuweisen) - etwas das vom Aufrufer bemerkt wird.

Und zum Thema Übergabe per Wert

Bei der Übergabe per Wert wird der Argumentausdruck ausgewertet und der resultierende Wert wird an die entsprechende Variable in der Funktion gebunden [...]. Wenn die Funktion oder Prozedur in der Lage ist, Werte an ihre Parameter zuzuweisen, wird nur ihre lokale Kopie [...].

Zweitens müssen wir wissen, was Java in seinen Methodenaufrufen verwendet. Die Java Language Specification besagt

Wenn die Methode oder der Konstruktor aufgerufen wird (§15.12), initialisieren die Werte der tatsächlichen Argumentausdrücke neu erstellte Parameter Variablen, jede vom deklarierten Typ, bevor der Körper der Methode oder des Konstruktors ausgeführt wird.

Es weist also den Wert des Arguments der entsprechenden Parametervariable zu.

Was ist der Wert des Arguments?

Lassen Sie uns Referenztypen betrachten, die Java Virtual Machine Specification besagt

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

Die Java Language Specification besagt auch

Die Referenzwerte (oft nur Verweise) sind Zeiger auf diese Objekte, und ein spezieller Null-Verweis, der auf kein Objekt verweist.

Der Wert eines Arguments (eines Referenztyps) ist ein Zeiger auf ein Objekt. Beachten Sie, dass eine Variable, ein Aufruf einer Methode mit einem Referenztyp-Rückgabetyp und ein Instanzerstellungsausdruck (new ...) alle zu einem Referenztyp-Wert auflösen.

Also

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

binden alle den Wert eines Verweises auf eine String-Instanz an den neu erstellten Parameter der Methode, param. Dies entspricht genau der Definition von Übergabe per Wert. Daher ist Java Übergabe per Wert.

Die Tatsache, dass Sie dem Verweis folgen können, um eine Methode aufzurufen oder auf ein Feld des referenzierten Objekts zuzugreifen, ist für das Gespräch vollkommen unerheblich. Die Definition von Übergabe per Referenz war

Dies bedeutet typischerweise, dass die Funktion die Variable, die als Argument verwendet wird, ändern kann (z.B. zuweisen) - etwas das vom Aufrufer bemerkt wird.

In Java bedeutet das Ändern der Variable, sie neu zuzuweisen. In Java würde ein erneutes Zuweisen der Variablen innerhalb der Methode vom Aufrufer unbemerkt bleiben. Das Ändern des Objekts, auf das die Variable verweist, ist ein vollständig anderer Konzept.


Primitive Werte sind ebenfalls in der Java Virtual Machine Specification definiert, hier. Der Wert des Typs ist der entsprechende ganze oder Fließkommawert, der entsprechend codiert ist (8, 16, 32, 64, usw. Bits).

55voto

Jared Oberhaus Punkte 14325

Sie können in Java niemals per Referenz übergeben, und eine der offensichtlichen Möglichkeiten ist, wenn Sie mehr als einen Wert von einem Methodenaufruf zurückgeben möchten. Betrachten Sie den folgenden Codeausschnitt in C++:

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

Manchmal möchten Sie dasselbe Muster in Java verwenden, aber das können Sie nicht; zumindest nicht direkt. Stattdessen könnten Sie etwas wie das folgende 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("Ergebnis: " + x[0] + " " + y[0]);
}

Wie in früheren Antworten erklärt wurde, in Java übergeben Sie einen Zeiger auf das Array als Wert an getValues. Das reicht aus, weil die Methode dann das Arrayelement ändert, und Sie erwarten per Konvention, dass Element 0 den Rückgabewert enthält. Offensichtlich können Sie dies auf andere Weise tun, beispielsweise indem Sie Ihren Code strukturieren, sodass dies nicht erforderlich ist, oder indem Sie eine Klasse erstellen, die den Rückgabewert enthalten oder ihn festlegen kann. Aber das einfache Muster, das Ihnen in C++ zur Verfügung steht, ist in Java nicht verfügbar.

CodeJaeger.com

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

Powered by:

X