Was ist der Zweck des Ausdrucks "new String (…)" in Java?

73

Beim Betrachten von Online-Codebeispielen bin ich manchmal auf eine Zuweisung einer String-Konstante zu einem String-Objekt mithilfe des neuen Operators gestoßen.

Zum Beispiel:

String s;
...
s = new String("Hello World");

Dies natürlich im Vergleich zu

s = "Hello World";

Ich bin mit dieser Syntax nicht vertraut und habe keine Ahnung, was der Zweck oder die Wirkung sein würde. Da String-Konstanten normalerweise im Konstantenpool und dann in der Darstellung gespeichert werden, die die JVM für den Umgang mit String-Konstanten hat, würde überhaupt etwas auf dem Heap zugewiesen werden?

Uri
quelle
1
Schauen Sie sich diesen Blog-Beitrag an. kjetilod.blogspot.com/2008/09/…
Ruggs
@Ruggs gut, danke für den Link, aber es wäre schön, wenn Sie den Haftungsausschluss über die Vorbehalte hinzufügen würden, wie dieser Typ .
2
Unabhängig von den Antworten, wie / warum verwendet new String(String)werden soll, ist s = new String("Hello World")der Parameter ein Literal, was in Java nicht sinnvoll ist und wahrscheinlich auch nie sinnvoll sein wird.
Maarten Bodewes

Antworten:

79

Der einzige Ort, an dem Sie vielleicht denken, dass Sie möchten, new String(String)ist das Erzwingen einer eindeutigen Kopie des internen Zeichenarrays, wie in

small=new String(huge.substring(10,20))

Dieses Verhalten ist jedoch leider nicht dokumentiert und von der Implementierung abhängig.

Ich bin dadurch verbrannt worden, als ich große Dateien (einige bis zu 20 MiB) in einen String gelesen und nachträglich in Zeilen geschnitten habe. Am Ende hatte ich alle Zeichenfolgen für die Zeilen, die auf das Zeichen [] verweisen, das aus der gesamten Datei besteht. Leider wurde dadurch für die wenigen Zeilen, an denen ich mich länger als bei der Verarbeitung der Datei festhielt, ungewollt auf das gesamte Array verwiesen - ich musste es umgehen new String(), da die Verarbeitung von 20.000 Dateien sehr schnell sehr viel RAM verbrauchte.

Der einzige implementierungsunabhängige Weg, dies zu tun, ist:

small=new String(huge.substring(10,20).toCharArray());

Dies muss das Array leider zweimal kopieren, einmal für toCharArray()und einmal im String-Konstruktor.

Es muss eine dokumentierte Möglichkeit geben, einen neuen String zu erhalten, indem die Zeichen eines vorhandenen Strings kopiert werden. oder die Dokumentation von String(String)muss verbessert werden, um sie expliziter zu machen (es gibt eine Implikation, aber sie ist ziemlich vage und offen für Interpretationen).

Fallstricke der Annahme, was der Doc nicht sagt

Beachten Sie als Antwort auf die Kommentare, die immer wieder eingehen, die Implementierung von Apache Harmony new String():

public String(String string) {
    value = string.value;
    offset = string.offset;
    count = string.count;
}

Das ist richtig, keine Kopie des zugrunde liegenden Arrays dort. Und dennoch entspricht es der (Java 7) String-Dokumentation, indem es:

Initialisiert ein neu erstelltes String-Objekt so, dass es dieselbe Zeichenfolge wie das Argument darstellt. Mit anderen Worten, die neu erstellte Zeichenfolge ist eine Kopie der Argumentzeichenfolge. Sofern keine explizite Kopie des Originals erforderlich ist, ist die Verwendung dieses Konstruktors nicht erforderlich, da Strings unveränderlich sind.

Das markante Stück ist „Kopie des Arguments Zeichenfolge “; Es heißt nicht "Kopie der Argumentzeichenfolge und des zugrunde liegenden Zeichenarrays, das die Zeichenfolge unterstützt".

Achten Sie darauf, dass Sie auf die Dokumentation und nicht auf eine Implementierung programmieren .

Lawrence Dol
quelle
"Dieses Verhalten ist jedoch leider nicht dokumentiert und von der Implementierung abhängig." In JavaDoc für den String(String)Konstruktor heißt es: "Initialisiert ein neu erstelltes String-Objekt so, dass es dieselbe Zeichenfolge wie das Argument darstellt. Mit anderen Worten, die neu erstellte Zeichenfolge ist eine Kopie der Argumentzeichenfolge. Sofern keine explizite Kopie des Originals erforderlich ist, Verwendung dieses Konstruktor ist nicht erforderlich , da Strings unveränderlich sind. " , das ist ein Kreisverkehr Art zu sagen , sagte Konstruktor eine explizite Kopie des underying macht char[]der Stringan sie übergeben.
Powerlord
7
@R. Bemrose. Nein, das könnte es bedeuten. Es heißt, dass Sie eine neue Kopie des String- Objekts erhalten - es gibt keine Aussage über den Inhalt des Objekts. Ein neuer String, der das zugrunde liegende Array gemeinsam nutzt, ist immer noch ein neuer String und eine Kopie des alten. Vergleichen String(char[] value)Sie dies mit einer expliziten Aussage : Der Inhalt des Zeichenarrays wird kopiert.
Lawrence Dol
1
@Monkey Ich bin nicht einverstanden mit Ihrer Interpretation des Javadoc. Wir können unsere booleschen Gleichungen hier nutzen: "Sofern keine explizite Kopie des Originals erforderlich ist, ist die Verwendung dieses Konstruktors nicht erforderlich" bedeutet !explicitCopyRequired --> constructor is unnecessary(-> bedeutet "impliziert") und die zu enthüllende Regel a --> b<==> . Wenn Sie den Konstruktor verwenden, benötigen Sie eine explizite Kopie. Ich stimme zu, dass es besser formuliert sein könnte, aber wenn Sie es aufschlüsseln, ist es klar, dass dieser Konstruktor vertraglich eine explizite Kopie erstellt. !b --> !aconstructor is necessary --> explicitCopyRequired
CorsiKa
2
@Glowcoder - machen Sie alle gewünschten Schlussfolgerungen, das ist nicht das, was JavaDoc angibt. Und IIRC mindestens eine wichtige JVM-Implementierung implementiert, new String(String)ohne eine Kopie des zugrunde liegenden Zeichenarrays zu erstellen - es könnte Apache Harmony gewesen sein, aber ich bin nicht sicher.
Lawrence Dol
9
Beachten Sie, dass in neueren Versionen der Oracle JVM-Teilzeichenfolgen das zugrunde liegende Array nicht wie in dieser Antwort beschrieben gemeinsam genutzt wird. substringKopiert stattdessen die Daten in ein neues Array. Dies begann in Java 1.7 Update 6. Siehe hier .
Lii
10

Das einzige Mal, dass ich dies nützlich fand, ist das Deklarieren von Sperrvariablen:

private final String lock = new String("Database lock");

....

synchronized(lock)
{
    // do something
}

In diesem Fall zeigen Debugging-Tools wie Eclipse die Zeichenfolge an, wenn aufgelistet wird, welche Sperren ein Thread derzeit enthält oder auf die er wartet. Sie müssen "new String" verwenden, dh ein neues String-Objekt zuweisen, da andernfalls ein gemeinsam genutztes String-Literal möglicherweise in einem anderen nicht verwandten Code gesperrt werden könnte.

Dave Ray
quelle
1
Ich denke, es ist besser, es zu verwenden private static class Lock {}; private final Lock lock = new Lock();, da der Klassenname so ziemlich überall auftaucht. Es kostet Sie jedoch ein paar K, da HotSPot nicht so effizient ist.
Tom Hawtin - Tackline
Das kann ich kaufen. Vielleicht probiere ich es das nächste Mal aus.
Dave Ray
12
Objektsperre = neues Objekt () macht das gleiche überhaupt ;-)
Hardcoded
@Hardcoded Richtig, außer dass es nicht Serializableso ist, also bevorzuge ich new Object[0].
Maaartinus
3
@maaartinus Was bringt es, Synchronisationsmonitore zu serialisieren? Die Synchronisierung zwischen den Instanzen funktioniert nicht, daher ist sie sinnlos. Im Gegensatz dazu sind meine Monitore immer endgültig.
Hardcoded
4

Das einzige von Software Monkey und Ruggs beschriebene Dienstprogramm für diesen Konstruktor scheint aus JDK7 verschwunden zu sein. offsetIn der Klasse String gibt es kein Feld mehr , und Teilzeichenfolgen werden immer verwendet

Arrays.copyOfRange(char[] original, int from, int to) 

um das char-Array für die Kopie zu kürzen.

Meister Töter
quelle
2
Bis und sofern dies nicht dokumentiert ist, verlassen Sie sich immer noch auf einen Implementierungsnebeneffekt und nicht auf eine dokumentierte Aktion. Nur weil JDK7 das Array zufällig kopiert, substring()muss es nicht in jeder Implementierung verwendet werden (und OpenJDK ist nicht die einzige JVM-Implementierung auf dem Markt ).
Lawrence Dol
In der Tat, aber das hier beschriebene Problem ist ein Nebeneffekt der jeweiligen Implementierung, nicht etwas, das irgendwo spezifiziert oder dokumentiert ist. Es ist also relevant.
MasterKiller
4

String s1 = "foo"; Literal wird in StringPool gehen und s1 wird verweisen.

String s2 = "foo"; Dieses Mal wird überprüft, ob das "foo" -Literal bereits in StringPool verfügbar ist oder nicht, da es jetzt existiert, sodass s2 auf dasselbe Literal verweist.

String s3 = neuer String ("foo"); Das Literal "foo" wird zuerst in StringPool erstellt, dann über den String-Arg-Konstruktor. Das String-Objekt wird erstellt, dh "foo" im Heap aufgrund der Objekterstellung durch einen neuen Operator, dann verweist s3 darauf.

String s4 = neuer String ("foo"); gleich wie s3

also System.out.println (s1 == s2); // wahr aufgrund eines wörtlichen Vergleichs.

und System.out.println (s3 == s4); // aufgrund des Objektvergleichs falsch (s3 und s4 werden an verschiedenen Stellen im Heap erstellt)

Vikas
quelle
4
Ich bin mir nicht sicher, warum dies positiv bewertet wird ... Es beantwortet die Frage in keiner Weise.
Christopher Schneider
Ich fand es hilfreich, weil es eine Frage beantwortete, die ich nicht finden konnte: "Machen Sie Strings, die mit dem Konstruktor erstellt wurden, überspringen Sie das Internieren"
beartech1
2

Nun, das hängt davon ab, was das "..." im Beispiel ist. Wenn es sich beispielsweise um einen StringBuffer oder ein Byte-Array oder etwas anderes handelt, wird aus den übergebenen Daten ein String erstellt.

Wenn es sich jedoch wie in nur um einen anderen String handelt, new String("Hello World!")sollte dieser "Hello World!"in jedem Fall durch einfach ersetzt werden. Strings sind unveränderlich, daher hat das Klonen eines Strings keinen Zweck - es ist nur ausführlicher und weniger effizient, ein neues String-Objekt zu erstellen, um als Duplikat eines vorhandenen Strings zu dienen (unabhängig davon, ob es sich um ein Literal oder eine andere bereits vorhandene String-Variable handelt).

Tatsächlich verwendet Effective Java (das ich sehr empfehle) genau dies als eines seiner Beispiele für "Vermeiden Sie das Erstellen unnötiger Objekte":


Betrachten Sie diese Aussage als extremes Beispiel dafür, was nicht zu tun ist:

String s = new String("stringette");  **//DON'T DO THIS!**

(Effektives Java, zweite Ausgabe)

Vineet
quelle
2
Nur weil Sie die Überladung verwenden, die eine Zeichenfolge enthält, bedeutet dies nicht, dass sie sinnlos ist - siehe die Antwort von Software Monkey.
Jon Skeet
0

Hier ist ein Zitat aus dem Buch Effective Java Third Edition (Punkt 17: Minimize Mutability):

Eine Folge der Tatsache, dass unveränderliche Objekte frei geteilt werden können, ist, dass Sie niemals defensive Kopien davon erstellen müssen (Punkt 50). Tatsächlich müssen Sie niemals Kopien erstellen, da die Kopien für immer den Originalen entsprechen würden. Daher müssen und sollten Sie keine Klonmethode oder keinen Kopierkonstruktor (Element 13) für eine unveränderliche Klasse bereitstellen. Dies wurde in den frühen Tagen der Java-Plattform nicht gut verstanden, daher verfügt die String-Klasse über einen Kopierkonstruktor, der jedoch selten, wenn überhaupt, verwendet werden sollte.

Es war also eine falsche Entscheidung von Java, da die StringKlasse unveränderlich ist. Sie hätte keinen Kopierkonstruktor für diese Klasse bereitstellen sollen. In Fällen, in denen Sie kostspielige Operationen an unveränderlichen Klassen ausführen möchten, können Sie öffentliche veränderbare Begleitklassen verwenden, die StringBuilderund StringBufferim Falle von String.

Taschkhisi
quelle
-1

Im Allgemeinen weist dies auf jemanden hin, der mit dem neuartigen C ++ - Deklarationsstil bei der Initialisierung nicht vertraut ist.

In den C-Tagen galt es nicht als gute Form, automatische Variablen in einem inneren Bereich zu definieren. C ++ beseitigte die Parser-Einschränkung und Java erweiterte diese.

Sie sehen also Code, der hat

int q;
for(q=0;q<MAX;q++){
    String s;
    int ix;
    // other stuff
    s = new String("Hello, there!");
    // do something with s
}

Im Extremfall befinden sich alle Deklarationen möglicherweise am Anfang einer Funktion und nicht in geschlossenen Bereichen wie der forSchleife hier.

Im Allgemeinen bewirkt dies jedoch, dass ein String ctor einmal aufgerufen und der resultierende String weggeworfen wird. (Der Wunsch, dies zu vermeiden, hat Stroustrup dazu veranlasst, Deklarationen an einer beliebigen Stelle im Code zuzulassen.) Sie haben also Recht, dass dies im besten Fall unnötig und schlecht und möglicherweise sogar schlecht ist.

Charlie Martin
quelle
4
Entweder verstehe ich das überhaupt nicht oder es hängt nicht mit der eigentlichen Frage zusammen. Die Frage betrifft den Unterschied zwischen ... = "Hallo" und ... = neuer String ("Hallo"). Sie scheinen über den Unterschied zwischen "String s = ..." und "String s; ...; s = ..." zu sprechen.
Dave Costa
Und ich muss von Herzen zustimmen, dass "damals, in den C-Tagen, es nicht als gute Form angesehen wurde, Auto-Variablen in einem inneren Bereich zu definieren", zumindest seit den C-Tagen von 1991; Nach meiner Erfahrung wurde es immer als am besten angesehen, Variablen in C mit einem so engen Umfang zu deklarieren, wie es der Compiler zulassen würde, was wiederum seit 1991 zumindest an der Spitze eines Blocks steht.
Lawrence Dol
-1

Es gibt zwei Möglichkeiten, wie Strings in Java erstellt werden können. Im Folgenden finden Sie Beispiele für beide Möglichkeiten: 1) Deklarieren Sie eine Variable vom Typ String (eine Klasse in Java) und weisen Sie sie einem Wert zu, der in doppelte Anführungszeichen gesetzt werden soll. Dadurch wird eine Zeichenfolge im Zeichenfolgenpoolbereich des Speichers erstellt. zB: String str = "JAVA";

2) Verwenden Sie den Konstruktor der String-Klasse und übergeben Sie einen String (in doppelten Anführungszeichen) als Argument. zB: String s = neuer String ("JAVA"); Dadurch wird eine neue Zeichenfolge JAVA im Hauptspeicher und auch im Zeichenfolgenpool erstellt, wenn diese Zeichenfolge nicht bereits im Zeichenfolgenpool vorhanden ist.

user2353653
quelle
Dann gibt es noch substring, trim, replace, replaceAll, StringBuilder, StringBuffer, usw.
Lawrence Dol
-2

Ich denke, es hängt von den Codebeispielen ab, die Sie sehen.

In den meisten Fällen wird mit dem Klassenkonstruktor "new String ()" im Codebeispiel nur eine sehr bekannte Java-Klasse angezeigt, anstatt eine neue zu erstellen.

Sie sollten es meistens vermeiden. Nicht nur, weil String-Literale interniert sind, sondern hauptsächlich, weil String unveränderlich sind. Es ist nicht sinnvoll, zwei Kopien zu haben, die dasselbe Objekt darstellen.

Obwohl der von Ruggs gemessene Artikel "interessant" ist , sollte er nur unter ganz bestimmten Umständen verwendet werden, da er mehr Schaden als Nutzen verursachen kann. Sie codieren eher für eine Implementierung als für eine Spezifikation, und derselbe Code kann beispielsweise in JRockit, IBM VM oder anderen nicht gleich ausgeführt werden.

OscarRyz
quelle
1
Es ist sinnvoll, zwei Exemplare zu haben, wenn Sie eines davon wegwerfen möchten, und es ist viel größer als das andere ...
Jon Skeet
1
Wenn eines größer als das andere ist, wären es an erster Stelle zwei verschiedene Objekte, nicht wahr? : - /
OscarRyz