PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Java String Instanzen


Monger
2005-07-14, 18:14:22
Manchmal sind es die kleinen Probleme die einen beschäftigen...

Ich habe in einem Programm in Java das Problem, dass ich mehr oder minder viele Objekte generiere, und sie ungern alle namentlich deklarieren will. Auf der anderen Seite möchte ich gerne schön simpel darauf zugreifen können. Mir kamen folgende drei Zeilen in den Sinn:


HashMap mapping = new HashMap();
mapping.put("test", new Object());

Object test = mapping.get("test");


Allem Anschein nach funktioniert es, aber warum? Das erste was ich in die HashMap reinschiebe, ist ganz klar eine Instanz eines Strings. Wenn ich aus der Map das Objekt rausziehe, lege ich aber doch eine neue Instanz von "test" an, oder etwa nicht ?
Wieso haben "test" und "test" die selbe ID, obwohl doch beides eigentlich frische Instanzen sind?

Ich hoffe ich hab mich verständlich ausgedrückt...

massa
2005-07-14, 18:36:09
Nunja, bisle kompliziert klingt deine Frage schon ;)

mapping.put("test", new Object());

"test" ist der Identifier und danach das Object, dass du reinsteckst

Object test = mapping.get("test");

Hier machst du ein neues Objekt namens test und holst anhand des Schlüssels "test" ein bestimmtes Objekt aus der HashMap mit dem du dein neues Objekt test versorgst.

aber eigentlich solltest du einen Cast durchführen, wenn du ein Objekt aus der HashMap holst

Object test = (Object) mapping.get("test");


Ich hoffe das war das was du wissen wolltest, ansonsten hab ich dich wirklich falsch verstanden :)

Wobei ich nicht weiß wie das ganze hier zu deiner Problemlösung beitragen soll

Pinoccio
2005-07-14, 18:39:05
Ich hoffe ich hab mich verständlich ausgedrückt...Dauert etwas ...
Ursache ist die Stringverwaltung von JAVA, die identische Strings nur einmal vorhält.
Deshalb kann mit
String s1="test";
String s2="test";
auch s1==s2 sein.

Mit allen anderen Objekten dürfte das afaik nicht gehen.

/edit: Link (http://64.233.183.104/search?q=cache:_-wPwoHsAqAJ:www.uni-klu.ac.at/~thaichho/java/k100037.html+java+strings+einmal&hl=en&client=firefox-a)

mfg Sebastian

HajottV
2005-07-14, 18:49:04
Wieso haben "test" und "test" die selbe ID, obwohl doch beides eigentlich frische Instanzen sind?

Hallo Monger...

das liegt daran, daß für die String Klasse in Java entsprechende hashCode und equals Methoden so implementiert sind, daß Strings mit gleichem Wert gleich behandelt werden. Diese Methoden werden dazu benutzt, um in der HashMap nach einem Eintrag zu suchen.

Wenn man die equals und hashCode Methode nicht überläd, werden die Adressen der Objekte verglichen.

Gruß

Jörg

PS: Mit der Stringverwaltung hat das nicht unbedingt was zu tun. Das ganze geht auch, wenn man die Strings von irgendwo einliest und nicht die intern() Representation benutzt.

PatkIllA
2005-07-14, 19:05:32
man kann sich doch sogar den Quellcode anschauen. Und da wird wie bereits gesagt auf hashCode() zurückgegriffen.

Wenn du eine extra Klasse für den Key nimmst und da drin dann public int hashCode() {
return 123456;
}reinschreibst kriegst du bei völlig verschiedenen Keyobjekten wieder das eingespeicherte Objekt zurück.

SGT.Hawk
2005-07-14, 19:08:05
Ausserdem legt Java String Konstanten in einem Pool an,um schneller drauf zuzugreifen.Das heisst gleiche StringKonstanten werden nicht neu instantiert.

Pinoccio
2005-07-14, 19:19:52
An HajottV:
# Literal strings within the same class (§8) in the same package (§7) represent references to the same String object (§4.3.1).
# Literal strings within different classes in the same package represent references to the same String object.
# Literal strings within different classes in different packages likewise represent references to the same String object.
# Strings computed by constant expressions (§15.28) are computed at compile time and then treated as if they were literals.
# Strings computed by concatenation at run time are newly created and therefore distinct.Also ich würde die im gewissen Sinne offizielle Aussage (http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#101083) eher in meiner Deutung verstehen als in deiner Auslegung.

mfg Sebastian

HellHorse
2005-07-14, 20:36:58
@Pinoccio
HajottV hat recht. Collections sind sinnigerweise per #equals definiert.

http://java.sun.com/j2se/1.5.0/docs/api/java/util/Map.html
Many methods in Collections Framework interfaces are defined in terms of the equals method. For example, the specification for the contains(Object key) method says: "returns true if and only if this map contain a mapping for a key k such that (key==null ? k==null : key.equals(k))." This specification should not be construed to imply that invoking Map.containsKey with a non-null argument key will cause key.equals(k) to be invoked for any key k. Implementations are free to implement optimizations whereby the equals invocation is avoided, for example, by first comparing the hash codes of the two keys. (The Object.hashCode() specification guarantees that two objects with unequal hash codes cannot be equal.) More generally, implementations of the various Collections Framework interfaces are free to take advantage of the specified behavior of underlying Object methods wherever the implementor deems it appropriate.

Pinoccio
2005-07-14, 21:34:49
@Pinoccio
HajottV hat recht. Collections sind sinnigerweise per #equals definiert.IIch vermute wir reden aneinander vorbei.
Ich rede von "Gleicheit" bei Strings und er/ihr von der Unterscheidung der Keys bei Maps.
Möglicherweise liegt das daran, daß sich Monger nicht verständlich ausgedrückt ausgedrückt hat. ;-)
Ansonsten sehe ich keine Unterschied zwischen meinen und euren Ausagen, schlicht weil ich ja nur über Strings rede.
Allerdings muß ich nach nun mehrmalige Lesen zugeben, daß mein Beitrag vermutlich nichts mit Mongers eigentlicher Frage zu tun hat.

Etwas mehr Code und eine präziesere Erklärung von der Seite wären hilfreich.

mfg Sebastian

HellHorse
2005-07-14, 22:25:08
Wenn du eine extra Klasse für den Key nimmst und da drin dann public int hashCode() {
return 123456;
}reinschreibst kriegst du bei völlig verschiedenen Keyobjekten wieder das eingespeicherte Objekt zurück.
Nein, da eine hash Funktion nie eindeutig sein kann, wenn schon, dann müsste man auch das machen
public boolean equals(Object o) {
return true;
}

PatkIllA
2005-07-14, 22:32:01
@HellHorse
na gut die hatte ich vorher schon drin.
Bei einfachen implementationen hätte sogar das equals gereicht. Das dürfte dann aber herrlich ineffizient werden, wenn man wirklich alle keys vergleichen muss.

Monger
2005-07-14, 23:16:58
Ich hab ein Weilchen über die Geschichte nachgedacht...

Meine Frage war, weshalb zwei Strings, die die selbe Zeichenkette enthalten, tatsächlich das selbe sind. Also nicht nur:


String s1 = "test";
String s2 = "test";

s.equals(test); // wahr

sondern auch

s1 == s2 // wahr


Ich hab ein bißchen im Gedächtnis gekramt, und Aussagen meines Java Dozenten zusammengeflickt.
Ich bin mir ziemlich sicher, dass Strings als eine Art "Singleton" angelegt sind. Die erste Instanz wird noch erzeugt, jedes weitere Mal wenn der Konstruktor mit einem bekannten String aufgerufen wird, wird auf den bestehenden String verwiesen. So legt sich Java im Hintergrund eine Art Wörterbuch an, worauf sehr schnell zugegriffen werden kann.

Afaik ist das so, weil man Strings aufs parsen optimiert hat, wo ja regelmäßig sehr oft die selben Schlüsselwörter auftauchen, und miteinander verglichen werden müssen. Deshalb ist String auch immutable.


Ich war am Anfang nur etwas verwirrt, weil wenn ich sage: "String s = new String()", nehme ich normalerweise an, dass da auch eine neue Instanz raus kommt...

Edit: Glückwunsch @ Pinoccio! Du bist am ehesten aus meinem Gestammel schlau geworden! ;)

Pinoccio
2005-07-14, 23:51:44
Glückwunsch @ Pinoccio! Du bist am ehesten aus meinem Gestammel schlau geworden! ;) :smile:
Bist ja nicht der erste, der sich wundert.
Kenne das noch aus dem Informatikunterricht, muß so 98 gewesen sein,
mein Anfang mit OOP:"Herr Lehrer! Warum darf ich String==String nicht machen? Funktioniert doch!" ...

mfg Sebastian

][immy
2005-07-15, 06:44:49
Manchmal sind es die kleinen Probleme die einen beschäftigen...

Ich habe in einem Programm in Java das Problem, dass ich mehr oder minder viele Objekte generiere, und sie ungern alle namentlich deklarieren will. Auf der anderen Seite möchte ich gerne schön simpel darauf zugreifen können. Mir kamen folgende drei Zeilen in den Sinn:


HashMap mapping = new HashMap();
mapping.put("test", new Object());

Object test = mapping.get("test");



So durchleuchten wir mal den Code
erste Zeile:
Du erzeugst eine neue Hashmap
zweite Zeile:
Du legst in die Hashmap ein neues Objekt rein, welches den Key "test" hat
dritte codezeile:
du erzeugst ein neues Objekt dem du die Referenz von deinem Vorherigen Objekt gibst

Mit anderen worten du Kopierst nicht das Objekt sondern du übergibst an dei neues Objekt nur die adresse des alten objektes, daher sind diese dann völlig identisch

Bei komplexen Datentypen (dazu gehört auch Objekt) wird das Objekt durch ein "=" immer nur Referenziert (die speicheradresse wird übergeben bzw beide Objekte zeigen auf die gleiche Speicherstelle) und keine kopie des Objektes angelegt


irre ich mich oder meistest du das?
dies geschieht nur bei simplen datentypen (z.B. int, string, double etc)

Monger
2005-07-15, 08:28:36
[immy']...
irre ich mich oder meistest du das?
dies geschieht nur bei simplen datentypen (z.B. int, string, double etc)

Ich hätte das Beispiel mit der HashMap weglassen sollen...
"string" gibt es übrigens in Java nicht (es sei denn ich hab da was fürchterlich übersehen), sondern nur "String" - also ein Objekt. In .NET ist "string" witzigerweise wieder ein Basisdatentyp...

Machen wir's noch noch einmal etwas ausführlicher:


HashMap<String, Object> mapping = new HashMap<String, Object>();
String s1 = "test"; // Ein String
String s2 = "test"; // Ein anderer String

mapping.put(s1, new Object());

Object testObjekt = mapping.get(s2);


testObjekt ist jetzt mein Objekt was ich vorher reingestopft habe. Aber "eigentlich" müsste es null sein, weil es in der HashMap eben keinen Value zum Key s2 gibt, sondern nur einen Value zum Key s1.

Wenn ich die Antworten hier richtig verstehe, gibt es dafür zwei Gründe:

1) Strings mit dem selben Inhalt verweisen tatsächlich auf die selbe Objektreferenz
2) HashMaps akzeptieren als Key nicht nur GENAU das Objekt was man ursprünglich reingestopft hat, sondern auch Objekte die genauso aussehen, d.h. wo "equals" inhaltliche Gleichheit feststellt.

Deshalb ist das Beispiel irgendwie doof gewählt, weil es zwei völlig unterschiedliche Phänomene mischt.

][immy
2005-07-15, 09:32:41
Ich hätte das Beispiel mit der HashMap weglassen sollen...
"string" gibt es übrigens in Java nicht (es sei denn ich hab da was fürchterlich übersehen), sondern nur "String" - also ein Objekt. In .NET ist "string" witzigerweise wieder ein Basisdatentyp...

Machen wir's noch noch einmal etwas ausführlicher:


HashMap<String, Object> mapping = new HashMap<String, Object>();
String s1 = "test"; // Ein String
String s2 = "test"; // Ein anderer String

mapping.put(s1, new Object());

Object testObjekt = mapping.get(s2);


testObjekt ist jetzt mein Objekt was ich vorher reingestopft habe. Aber "eigentlich" müsste es null sein, weil es in der HashMap eben keinen Value zum Key s2 gibt, sondern nur einen Value zum Key s1.

Wenn ich die Antworten hier richtig verstehe, gibt es dafür zwei Gründe:

1) Strings mit dem selben Inhalt verweisen tatsächlich auf die selbe Objektreferenz
2) HashMaps akzeptieren als Key nicht nur GENAU das Objekt was man ursprünglich reingestopft hat, sondern auch Objekte die genauso aussehen, d.h. wo "equals" inhaltliche Gleichheit feststellt.

Deshalb ist das Beispiel irgendwie doof gewählt, weil es zwei völlig unterschiedliche Phänomene mischt.

wenn du 2 Strings mit dem gleichen inhalt erzeugts haben beiden Strings den gleichen inhalt aber nicht die gleiche speicheradresse, also die strings sind zwar vom inhalt gleich aber nicht identisch. so eine funktion wäre auch sehr tötdlich für die meisten programme das 2 strings die erstmal den gleichen inhalt haben auch die gleichen speicheradresse haben. ändert man einen der strings würde das heißen das der zweite auch geändert werden würde. Strings gehören immernoch zu den primitiven datentypen (obwohl es eher ein char-Array ist) und wird daher nicht referenziert sondern kopiert

wenn du aus der Hashmap ein objekt wieder rausholen willst und dafür einen String übergibst, wird das Value des Strings benutzt, die Speicheradresse (Referenz) hat damit nichts zu tun. d.h. solange in beiden Strings das gleiche drin steht funktioniert das ganze auch.


PS:
mit der Groß und Kleinschreibung bei mir nicht so genau nehmen, wenn ich mal eben was schnell tippe, dann achte ich nicht besonders darauf (würde ich Code schreibne würde ich aber drauf achten)

Monger
2005-07-15, 10:02:43
[immy']wenn du 2 Strings mit dem gleichen inhalt erzeugts haben beiden Strings den gleichen inhalt aber nicht die gleiche speicheradresse, also die strings sind zwar vom inhalt gleich aber nicht identisch.

Ich denke, da irrst du dich.


so eine funktion wäre auch sehr tötdlich für die meisten programme das 2 strings die erstmal den gleichen inhalt haben auch die gleichen speicheradresse haben. ändert man einen der strings würde das heißen das der zweite auch geändert werden würde.

Genau deshalb sind Strings immutable.


String s = "test";
s += "1";

Letzte Zeile nimmt nicht etwa den alten String, und ergänzt ihn durch eine "1", sondern instanziiert einen neuen String mit dem Inhalt beider voriger Strings, und weist s die neue Referenz zu.


Strings gehören immernoch zu den primitiven datentypen (obwohl es eher ein char-Array ist) und wird daher nicht referenziert sondern kopiert

Und ich sage es noch einmal: "String" ist KEIN Basisdatentyp!
Wenn du es nicht glaubst:
http://java.sun.com/j2se/1.5.0/docs/api/java/lang/String.html

Wir hatten es hier schon ein paar Mal davon, dass "String" sich auch nicht gerade wie ein typisches "Object" verhält, aber es ist definitiv eine Klasse mit Attributen und Methoden. Und alle Objekte von Klassen werden referenziert, nicht kopiert.

][immy
2005-07-15, 12:27:41
Ich denke, da irrst du dich.


Genau deshalb sind Strings immutable.


String s = "test";
s += "1";

Letzte Zeile nimmt nicht etwa den alten String, und ergänzt ihn durch eine "1", sondern instanziiert einen neuen String mit dem Inhalt beider voriger Strings, und weist s die neue Referenz zu.


Und ich sage es noch einmal: "String" ist KEIN Basisdatentyp!
Wenn du es nicht glaubst:
http://java.sun.com/j2se/1.5.0/docs/api/java/lang/String.html

Wir hatten es hier schon ein paar Mal davon, dass "String" sich auch nicht gerade wie ein typisches "Object" verhält, aber es ist definitiv eine Klasse mit Attributen und Methoden. Und alle Objekte von Klassen werden referenziert, nicht kopiert.

Das ist mir schon klar, aber ein String verhält sich wie ein primitiver datentyp

String x = "test";
String z = x;

x = "test2";

danach wird z immernoch gleich "test" sein, da wie du schon geschrieben hast immer ein neues Objekt erzeugt wird (anfügen ginge nur mit einem StringBuilder bzw Stringbuffer). trotzdem wird bei der Übergabe von Strings immer eine Kopie angefertigt.
Ich schreib es mal so, String ist ein komplexer Datentyp mit dem Verhalten eines primitiven Datentyps.

ich weiß auch durchaus das man Strings nicht mit == vergleichen sollte, allerdings existiert dieses problem nicht mehr seit einiger zeit. nur auf älteren Java-versionen könnte es probleme damit geben.

anderes beispiel wäre, wenn du den String ausgibst (wobei ich mich immoment nicht mehr an den javabefehl erinner) gibst du nicht befehl(x) (x für einen beliebigen string). Würde sich ein String Objekt wie ein komplexer Datentyp verhalten, müsste jetzt der Datentyp ausgegeben werden und nicht der Inhalt des Strings, aber da sich ein String wie ein primitiver Datentyp verhält wird der Inhalt des Strings-Objektes ausgegeben.

PatkIllA
2005-07-15, 12:33:40
Bei der Ausgabe wird die toString-Methode aufgerufen und die gibt bei Strings eben den Inhalt wieder und nicht wie standardmäßig Klassenname und hashcode. Kann man auch wunderbar bei eigenen Klassen überschreiben.

HajottV
2005-07-15, 13:46:14
[immy']trotzdem wird bei der Übergabe von Strings immer eine Kopie angefertigt.

Nein. Bei Strings wird immer die Referenz übergeben. String ist ein komplexer Datentyp.

[immy']
ich weiß auch durchaus das man Strings nicht mit == vergleichen sollte, allerdings existiert dieses problem nicht mehr seit einiger zeit. nur auf älteren Java-versionen könnte es probleme damit geben.


Damit wirst Du auch bei neueren Java-Versionen Probleme bekommen. "==" vergleicht auf Identität.

[immy']Würde sich ein String Objekt wie ein komplexer Datentyp verhalten, müsste jetzt der Datentyp ausgegeben werden und nicht der Inhalt des Strings, aber da sich ein String wie ein primitiver Datentyp verhält wird der Inhalt des Strings-Objektes ausgegeben.

Man kann jeden komplexen Datentyp so überladen, daß bei toString() ein Inhalt ausgegeben wird.

Gruß

Jörg

HajottV
2005-07-15, 13:48:11
1) Strings mit dem selben Inhalt verweisen tatsächlich auf die selbe Objektreferenz


Nein, das stimmt nicht.


2) HashMaps akzeptieren als Key nicht nur GENAU das Objekt was man ursprünglich reingestopft hat, sondern auch Objekte die genauso aussehen, d.h. wo "equals" inhaltliche Gleichheit feststellt.


Die Method hashCode muß auch passen, aber das kann man so stehen lassen.

Gruß

Jörg

Xmas
2005-07-15, 21:46:11
[immy']Das ist mir schon klar, aber ein String verhält sich wie ein primitiver datentyp

String x = "test";
String z = x;

x = "test2";

danach wird z immernoch gleich "test" sein
Das ist ja mal ein ganz schlechtes Beispiel. Bei absolut keinem Datentypen wird nach diesem bzw. äquivalentem Codeabschnitt z gleich x sein. Das gilt sowohl für primitive als auch komplexe Datentypen (bzw. Wert- und Referenztypen).

PH4Real
2005-07-17, 23:54:22
Damit wirst Du auch bei neueren Java-Versionen Probleme bekommen. "==" vergleicht auf Identität.


Probleme? Probleme ist gar kein Ausdruck...


Integer i1 = 127;
Integer i2 = 127;

// TRUE... Ok autoboxing is cool...
System.out.println(i1 == i2);

i1 = 128;
i2 = 128;

// FALSE!? Are you kidding me..?!
System.out.println(i1 == i2);


Das funktioniert natürlich erst ab Java 5 so... Welcome to Java 5! (and happy debugging) :ugly:

EDIT: Sorry für leicht OT aber ich komme immer noch nicht darüber hinweg, dass sowas in Java möglich ist...