Übergeben eines Strings als Referenz in Java?

160

Ich bin es gewohnt, Folgendes zu tun C:

void main() {
    String zText = "";
    fillString(zText);
    printf(zText);
}

void fillString(String zText) {
    zText += "foo";
}

Und die Ausgabe ist:

foo

In Java scheint dies jedoch nicht zu funktionieren. Ich gehe davon aus, dass das StringObjekt kopiert wird, anstatt von referenziert übergeben zu werden . Ich dachte, Strings wären Objekte, die immer als Referenz übergeben werden.

Was geht hier vor sich?

Soren Johnson
quelle
2
Selbst wenn sie als Referenz übergeben wurden (was in Java übergeben wird, ist eine Kopie des Referenzwerts, aber das ist ein anderer Thread). String-Objekte sind unveränderlich, so dass das sowieso nicht funktionieren würde
OscarRyz
8
Es ist kein C-Code.
Alston
@Phil: Dies funktioniert auch in C # nicht.
Mangesh

Antworten:

196

Sie haben drei Möglichkeiten:

  1. Verwenden Sie einen StringBuilder:

    StringBuilder zText = new StringBuilder ();
    void fillString(StringBuilder zText) { zText.append ("foo"); }
  2. Erstellen Sie eine Containerklasse und übergeben Sie eine Instanz des Containers an Ihre Methode:

    public class Container { public String data; }
    void fillString(Container c) { c.data += "foo"; }
  3. Erstellen Sie ein Array:

    new String[] zText = new String[1];
    zText[0] = "";
    
    void fillString(String[] zText) { zText[0] += "foo"; }

Aus Sicht der Leistung ist der StringBuilder normalerweise die beste Option.

Aaron Digulla
quelle
11
Denken Sie daran, dass StringBuilder nicht threadsicher ist. Verwenden Sie in einer Multithread-Umgebung die StringBuffer-Klasse oder kümmern Sie sich manuell um die Synchronisierung.
Boris Pavlović
12
@ Boris Pavlovic - ja, aber wenn nicht derselbe StringBuilder von verschiedenen Threads verwendet wird, was IMO in einem bestimmten Szenario unwahrscheinlich ist, sollte dies kein Problem sein. Es spielt keine Rolle, ob die Methode von verschiedenen Threads aufgerufen wird und unterschiedliche StringBuilder empfängt.
Ravi Wallau
Ich mag die dritte Option (Array) am meisten, weil es die einzige allgemeine ist. Es ermöglicht auch die Übergabe von Boolean, Int usw. Können Sie bitte die Leistung dieser Lösung etwas ausführlicher erläutern - Sie behaupten, dass die erste aus Sicht der Leistung besser ist.
Meolic
@meolic StringBuilderist schneller, s += "..."da nicht jedes Mal neue String-Objekte zugewiesen werden. In der Tat, wenn Sie Java-Code wie schreiben s = "Hello " + name, dann generiert der Compiler Byte-Code, der ein erstellt StringBuilderund append()zweimal aufruft .
Aaron Digulla
85

In Java wird nichts als Referenz übergeben . Alles wird als Wert übergeben . Objektreferenzen werden als Wert übergeben. Zusätzlich sind Strings unveränderlich . Wenn Sie also an die übergebene Zeichenfolge anhängen, erhalten Sie nur eine neue Zeichenfolge. Sie können einen Rückgabewert verwenden oder stattdessen einen StringBuffer übergeben.

Zedoo
quelle
2
Dies ist kein Missverständnis, es ist ein Konzept
Patrick Cornelissen
41
Ummm..no, es ist ein Missverständnis, dass Sie ein Objekt als Referenz übergeben. Sie übergeben eine Referenz nach Wert.
Ed S.
30
Ja, das ist ein Missverständnis. Es ist ein großes, weit verbreitetes Missverständnis. Es führt zu einer Interviewfrage, die ich hasse: ("Wie übergibt Java Argumente?"). Ich hasse es, weil ungefähr die Hälfte der Interviewer tatsächlich die falsche Antwort zu wollen scheint ("Primitive nach Wert, Objekte nach Referenz"). Die richtige Antwort dauert länger und scheint einige von ihnen zu verwirren. Und sie werden nicht überzeugt sein: Ich schwöre, ich bin durch einen technischen Bildschirm durchgefallen, weil der CSMajor-Screener das Missverständnis im College gehört und es als Evangelium geglaubt hatte. Feh.
CPerkins
4
@CPerkins Du hast keine Ahnung, wie wütend mich das macht. Es bringt mein Blut zum Kochen.
6
Alles wird in allen Sprachen als Wert übergeben. Einige dieser Werte sind Adressen (Referenzen).
Gerardw
52

Was passiert ist, dass die Referenz als Wert übergeben wird, dh eine Kopie der Referenz wird übergeben. In Java wird nichts als Referenz übergeben. Da eine Zeichenfolge unveränderlich ist, wird durch diese Zuweisung ein neues Zeichenfolgenobjekt erstellt, auf das die Kopie der Referenz jetzt verweist. Die ursprüngliche Referenz zeigt immer noch auf die leere Zeichenfolge.

Dies ist für jedes Objekt gleich, dh es wird in einer Methode auf einen neuen Wert gesetzt. Das folgende Beispiel macht nur deutlich, was vor sich geht, aber das Verketten einer Zeichenfolge ist wirklich dasselbe.

void foo( object o )
{
    o = new Object( );  // original reference still points to old value on the heap
}
Ed S.
quelle
6
Exzellentes Beispiel!
Nickolas
18

java.lang.String ist unveränderlich.

Ich hasse es, URLs einzufügen, aber https://docs.oracle.com/javase/10/docs/api/java/lang/String.html ist wichtig, damit Sie lesen und verstehen können, wenn Sie sich in Java-Land befinden.

Ryan Fernandes
quelle
Ich glaube nicht, dass Soren Johnson nicht weiß, was ein String ist, das war nicht die Frage und nicht das, wonach ich nach 15 Jahren Java gesucht habe.
AHH
8

Objekte werden als Referenz übergeben, Grundelemente als Wert.

String ist kein Grundelement, es ist ein Objekt und es ist ein Sonderfall eines Objekts.

Dies dient dem Speicherbedarf. In JVM gibt es einen Zeichenfolgenpool. Für jede erstellte Zeichenfolge versucht JVM zu prüfen, ob dieselbe Zeichenfolge im Zeichenfolgenpool vorhanden ist, und zeigt darauf, wenn bereits eine vorhanden ist.

public class TestString
{
    private static String a = "hello world";
    private static String b = "hello world";
    private static String c = "hello " + "world";
    private static String d = new String("hello world");

    private static Object o1 = new Object();
    private static Object o2 = new Object();

    public static void main(String[] args)
    {
        System.out.println("a==b:"+(a == b));
        System.out.println("a==c:"+(a == c));
        System.out.println("a==d:"+(a == d));
        System.out.println("a.equals(d):"+(a.equals(d)));
        System.out.println("o1==o2:"+(o1 == o2));

        passString(a);
        passString(d);
    }

    public static void passString(String s)
    {
        System.out.println("passString:"+(a == s));
    }
}

/* AUSGABE */

a==b:true
a==c:true
a==d:false
a.equals(d):true
o1==o2:false
passString:true
passString:false

Das == prüft auf Speicheradresse (Referenz) und das .equals prüft auf Inhalt (Wert).

janetsmith
quelle
3
Nicht ganz richtig. Java wird immer von VALUE übergeben. Der Trick besteht darin, dass Objekte als Referenz übergeben werden, die als Wert übergeben werden. (knifflig, ich weiß). Überprüfen Sie dieses: javadude.com/articles/passbyvalue.htm
adhg
1
@adhg Sie haben geschrieben "Objekte werden als Referenz übergeben, die als Wert übergeben werden." <--- das ist Kauderwelsch. Sie meinen, Objekte haben Referenzen, die als Wert übergeben werden
barlop
2
-1 Sie haben geschrieben "Objekte werden als Referenz übergeben" <- FALSE. Wenn dies wahr wäre, könnte die Methode beim Aufrufen einer Methode und beim Übergeben einer Variablen die Variable auf ein anderes Objekt verweisen lassen, kann dies jedoch nicht. Alles wird in Java als Wert übergeben. Und Sie sehen immer noch den Effekt der Änderung der Attribute eines Objekts außerhalb der Methode.
Barlop
Siehe die Antwort hier stackoverflow.com/questions/40480/…
Barlop
7

Alle Argumente in Java werden als Wert übergeben. Wenn Sie einen Pass Stringauf eine Funktion, dass der Wert übergeben werden ist ein Verweis auf ein String - Objekt, aber Sie können diese Referenz nicht ändern, und das zugrundeliegende String - Objekt ist unveränderlich.

Die Zuordnung

zText += foo;

ist äquivalent zu:

zText = new String(zText + "foo");

Das heißt, es zTextweist den Parameter (lokal) als neue Referenz neu zu, die auf einen neuen Speicherort verweist, in dem sich ein neuer befindet String, der den ursprünglichen Inhalt von zTextmit "foo"angehängt enthält.

Das ursprüngliche Objekt wird nicht geändert, und die main()lokale Variable der Methode zeigt zTextweiterhin auf die ursprüngliche (leere) Zeichenfolge.

class StringFiller {
  static void fillString(String zText) {
    zText += "foo";
    System.out.println("Local value: " + zText);
  }

  public static void main(String[] args) {
    String zText = "";
    System.out.println("Original value: " + zText);
    fillString(zText);
    System.out.println("Final value: " + zText);
  }
}

Drucke:

Original value:
Local value: foo
Final value:

Wenn Sie die Zeichenfolge ändern möchten, können Sie wie angegeben StringBuildereinen Container (ein Array oder eine AtomicReferenceoder eine benutzerdefinierte Containerklasse) verwenden, der Ihnen eine zusätzliche Ebene der Zeiger-Indirektion bietet. Alternativ geben Sie einfach den neuen Wert zurück und weisen ihn zu:

class StringFiller2 {
  static String fillString(String zText) {
    zText += "foo";
    System.out.println("Local value: " + zText);
    return zText;
  }

  public static void main(String[] args) {
    String zText = "";
    System.out.println("Original value: " + zText);
    zText = fillString(zText);
    System.out.println("Final value: " + zText);
  }
}

Drucke:

Original value:
Local value: foo
Final value: foo

Dies ist wahrscheinlich die Java-ähnlichste Lösung im allgemeinen Fall - siehe den effektiven Java- Punkt "Unveränderlichkeit begünstigen".

Wie bereits erwähnt, StringBuildererzielen Sie jedoch häufig eine bessere Leistung. Wenn Sie viel anhängen müssen, insbesondere innerhalb einer Schleife, verwenden Sie StringBuilder.

Versuchen Sie jedoch, unveränderlich Stringsund nicht veränderlich weiterzugeben, StringBuilderswenn Sie können - Ihr Code ist leichter zu lesen und besser zu warten. Überlegen Sie final, ob Sie Ihre Parameter festlegen und Ihre IDE so konfigurieren möchten, dass Sie gewarnt werden, wenn Sie einen Methodenparameter einem neuen Wert zuweisen.

David Moles
quelle
6
Sie sagen "streng genommen", als wäre es fast irrelevant - während es im Zentrum des Problems liegt. Wenn Java wirklich als Referenz übergeben worden wäre, wäre dies kein Problem. Bitte versuchen Sie zu vermeiden, den Mythos "Java übergibt Objekte als Referenz" zu verbreiten. Die Erklärung des (korrekten) Modells "Referenzen werden als Wert übergeben" ist viel hilfreicher, IMO.
Jon Skeet
Hey, wo ist mein vorheriger Kommentar geblieben? :-) Wie ich bereits sagte und wie Jon jetzt hinzugefügt hat, besteht das eigentliche Problem hier darin, dass die Referenz als Wert übergeben wird. Das ist sehr wichtig und viele Menschen verstehen den semantischen Unterschied nicht.
Ed S.
1
Meinetwegen. Als Java - Programmierer habe ich nichts gesehen tatsächlich verstrichen ist durch Bezugnahme in besser als ein Jahrzehnt, so dass meine Sprache schlampig worden. Aber Sie haben Recht, ich hätte mehr aufpassen sollen, besonders für ein C-Programmierpublikum.
David Moles
5

String ist ein unveränderliches Objekt in Java. Sie können die StringBuilder-Klasse wie folgt verwenden, um den Job auszuführen, den Sie ausführen möchten:

public static void main(String[] args)
{
    StringBuilder sb = new StringBuilder("hello, world!");
    System.out.println(sb);
    foo(sb);
    System.out.println(sb);

}

public static void foo(StringBuilder str)
{
    str.delete(0, str.length());
    str.append("String has been modified");
}

Eine andere Option besteht darin, eine Klasse mit einem String als Bereichsvariable (dringend empfohlen) wie folgt zu erstellen:

class MyString
{
    public String value;
}

public static void main(String[] args)
{
    MyString ms = new MyString();
    ms.value = "Hello, World!";

}

public static void foo(MyString str)
{
    str.value = "String has been modified";
}
Fadi Hanna AL-Kass
quelle
1
String ist in Java kein primitiver Typ.
VWeber
Stimmt, aber worauf genau wollen Sie meine Aufmerksamkeit lenken?
Fadi Hanna AL-Kass
Seit wann ist Java String ein Grundelement?! SO! Zeichenfolge als Referenz übergeben.
Der
2
"Unveränderliches Objekt" wollte ich sagen. Aus irgendeinem Grund habe ich meine Antwort nicht erneut gelesen, nachdem @VWeber einen Kommentar abgegeben hatte, aber jetzt sehe ich, was er zu sagen versuchte. mein Fehler. Antwort geändert
Fadi Hanna AL-Kass
2

Die Antwort ist einfach. In Java sind Strings unveränderlich. Daher ist es so, als würde man den Modifikator 'final' (oder 'const' in C / C ++) verwenden. Einmal zugewiesen, können Sie es also nicht mehr so ​​ändern, wie Sie es getan haben.

Sie können ändern, auf welchen Wert welche Zeichenfolge zeigt, aber Sie können NICHT den tatsächlichen Wert ändern, auf den diese Zeichenfolge gerade zeigt.

Dh. String s1 = "hey". Sie können machen s1 = "woah", und das ist völlig in Ordnung, aber Sie können den zugrunde liegenden Wert der Zeichenfolge (in diesem Fall: "hey") nicht ändern, um etwas anderes zu sein, sobald sie mit plusEquals usw. zugewiesen wurde (dh s1 += " whatup != "hey whatup").

Verwenden Sie dazu die Klassen StringBuilder oder StringBuffer oder andere veränderbare Container und rufen Sie dann einfach .toString () auf, um das Objekt wieder in eine Zeichenfolge zu konvertieren.

Hinweis: Zeichenfolgen werden häufig als Hash-Schlüssel verwendet, daher ist dies ein Teil des Grundes, warum sie unveränderlich sind.

ak_2050
quelle
Ob es veränderlich oder unveränderlich ist, ist nicht relevant. =Beeinflusst NIEMALS das Objekt, auf das es zeigt, unabhängig von der Klasse.
Newacct
2

String ist eine spezielle Klasse in Java. Es ist Thread-sicher, was bedeutet, dass "Sobald eine String-Instanz erstellt wurde, der Inhalt der String-Instanz niemals geändert wird".

Hier ist, was los ist

 zText += "foo";

Zuerst erhält der Java-Compiler den Wert der zText-String-Instanz und erstellt dann eine neue String-Instanz, deren Wert zText ist, wobei "foo" angehängt wird. Sie wissen also, warum sich die Instanz, auf die zText zeigt, nicht geändert hat. Es ist eine völlig neue Instanz. Tatsächlich ist sogar String "foo" eine neue String-Instanz. Für diese Anweisung erstellt Java zwei String-Instanzen, eine ist "foo", eine andere ist der Wert des zText-Anhangs "foo". Die Regel ist einfach: Der Wert der String-Instanz wird niemals geändert.

Für die Methode fillString können Sie einen StringBuffer als Parameter verwenden oder ihn folgendermaßen ändern:

String fillString(String zText) {
    return zText += "foo";
}
DeepNightTwo
quelle
... aber wenn Sie 'fillString' gemäß diesem Code definieren, sollten Sie ihm wirklich einen passenderen Namen geben!
Stephen C
Ja. Diese Methode sollte "appendString" oder so heißen.
DeepNightTwo
1

Zeichenfolgen sind in Java unveränderlich .

Grzegorz Oledzki
quelle
1

Dies funktioniert mit StringBuffer

public class test {
 public static void main(String[] args) {
 StringBuffer zText = new StringBuffer("");
    fillString(zText);
    System.out.println(zText.toString());
 }
  static void fillString(StringBuffer zText) {
    zText .append("foo");
}
}

Verwenden Sie StringBuilder noch besser

public class test {
 public static void main(String[] args) {
 StringBuilder zText = new StringBuilder("");
    fillString(zText);
    System.out.println(zText.toString());
 }
  static void fillString(StringBuilder zText) {
    zText .append("foo");
}
}
Mohammed Rafeeq
quelle
1

String ist in Java unveränderlich. Sie können ein vorhandenes Zeichenfolgenliteral / -objekt nicht ändern.

String s = "Hallo"; s = s + "hi";

Hier wird die vorherige Referenz s durch die neue Referenz s ersetzt, die auf den Wert "HelloHi" zeigt.

Um die Veränderlichkeit zu verbessern, haben wir jedoch StringBuilder und StringBuffer.

StringBuilder s = neuer StringBuilder (); s.append ("Hi");

Dadurch wird der neue Wert "Hi" an dieselbe Referenz angehängt. // //

gauravJ
quelle
0

Aaron Digulla hat bisher die beste Antwort. Eine Variation seiner zweiten Option ist die Verwendung des Wrappers oder der Containerklasse MutableObject der Commons Lang Library Version 3+:

void fillString(MutableObject<String> c) { c.setValue(c.getValue() + "foo"); }

Sie speichern die Deklaration der Containerklasse. Der Nachteil ist eine Abhängigkeit von der Commons Lang Lib. Aber die Bibliothek hat eine Menge nützlicher Funktionen und fast jedes größere Projekt, an dem ich gearbeitet habe, hat sie verwendet.

user2606740
quelle
0

Wenn Sie ein Objekt (einschließlich String) als Referenz in Java übergeben möchten, können Sie es als Mitglied eines umgebenden Adapters übergeben. Eine Lösung mit einem Generikum ist hier:

import java.io.Serializable;

public class ByRef<T extends Object> implements Serializable
{
    private static final long serialVersionUID = 6310102145974374589L;

    T v;

    public ByRef(T v)
    {
        this.v = v;
    }

    public ByRef()
    {
        v = null;
    }

    public void set(T nv)
    {
        v = nv;
    }

    public T get()
    {
        return v;
    }

// ------------------------------------------------------------------

    static void fillString(ByRef<String> zText)
    {
        zText.set(zText.get() + "foo");
    }

    public static void main(String args[])
    {
        final ByRef<String> zText = new ByRef<String>(new String(""));
        fillString(zText);
        System.out.println(zText.get());
    }
}
Sam Ginrich
quelle
0

Für jemanden, der neugieriger ist

class Testt {
    static void Display(String s , String varname){
        System.out.println(varname + " variable data = "+ s + " :: address hashmap =  " + s.hashCode());
    }

    static void changeto(String s , String t){
        System.out.println("entered function");
        Display(s , "s");
        s = t ;
        Display(s,"s");
        System.out.println("exiting function");
    }

    public static void main(String args[]){
        String s =  "hi" ;
        Display(s,"s");
        changeto(s,"bye");
        Display(s,"s");
    }
}

Wenn Sie nun den obigen Code ausführen, können Sie sehen, wie sich die Adresse hashcodesmit der String-Variablen s ändert. Ein neues Objekt wird der Variablen s in Funktion zugewiesen, changetowenn s geändert wird

Rajesh Kumar
quelle