Hallo,
Wieso sollte er dann StringBuffer verwenden?
Aus Performancegründen (wie ich begründet habe)
Ja, aber der Performancevorteil tritt ja nur dann auf, wenn der
String selbst verändert wird. (Wie ich begründet habe.)
Dann darfst du gerne StringBuffer verwenden. Aber wieso hier? Macht
die Sache doch nur viel aufwendiger. Außerdem hat man nicht den
schönen Vorteil, daß Strings intern effizient verwaltet werden.
Beispiel:
int count = 500000;
String[] sa = new String[count];
System.out.println(System.currentTimeMillis());
for (int i = 0; i < count; i++) {
sa[i] = new String("Hubbabubba");
}
System.out.println(System.currentTimeMillis());
StringBuffer[] sb = new StringBuffer[count];
for (int i = 0; i < count; i++) {
sb[i] = new StringBuffer("Hubbabubba");
}
System.out.println(System.currentTimeMillis());
Das Anlegen der String-Objekte ist ca. um den Faktor 4 schneller.
Ich würde außerdem behaupten, daß in diesem - zugegebenermaßen
sehr spezialisiertem Beispiel - weit weniger Speicher verbraucht wird.
Aber das konnte ich jetzt leider nicht nachweisen.
public static void concat() {
System.out.println("entering concat()...");
// for-Schleife entfaltet
result = arr[0]
.concat(arr[1]) // 1. temporäres String Object
.concat(arr[2]) // 2. temporäres String Object
.concat(arr[3]);
/* das bedeutet: für n Konkatenierungen mit .concat() werden
(unnötigerweise) n-1 temporäre String Objekte kreiert,
die dann wieder 'GC'-ed werden (müssen)!
Darunter dürfte sowohl die Performance leiden als auch das
Speicherprofil */
Richtig. Allerdings änderst du hier wieder einen String. In diesem Fall
verwendet man - wie du und ich ja korrekt gesagt festgestellt haben -
besser einen StringBuffer. Genau dafür ist er ja da.
Darum ging's aber ja nicht.
public static void cast() {
[...]
/* Du hast Recht (mit JDK 1.3.1) - allerdings dürfte dieser
* Performanceeffekt besonders von der VM-Implementierung abhängen.
*/
}
Auch mit JDK 1.4.1 und vermutlich allen vorangegangenen JVMs. :-)
Der Grund ist vermutlich einfach der, daß ein Cast viel weniger
Overhead erzeugt als ein Methodenaufruf (->toString). Natürlich hängt
das von der VM-Implementierung ab. Aber was hängt nicht von der
Implementierung ab? Wenn du Leute beim Implementieren eines Casts
Schrott programmieren, wird das natürlich langsam. Wenn die Leute
beim Implementieren von StringBuffer.append() Schrott bauen und
beispielsweise temporäre String-Objekte verwenden würden, wäre append()
auch langsamer. ;-)
Nein, man sollte das ganz sicher nicht so tun! Was ist, wenn er
irgendwo einen Programmierfehler hat und nun statt einem String
weißnicht ein Integer-Objekt in den Vector geschrieben hat. Ein
toString() würde für den Integer funktionieren.
Hannes deklarierte:
private Vector fErrorLog = new Vector();Daraus schloss ich spekulativ, der Programmierer beabsichtigt dem Vektor werden ausschließlich String Objekte
hinzuzufügen und macht dies auch
Zweifellos.
(der 'private' access modifier ließ mich zusätzlich
vermuten, der Vektor stellt ein internes Member dar).
Naja, mich nicht. Möglich wär's aber. :-)
Darum ging es mir aber nicht. Es ging mir allgemein um die Verwendung
von toString statt einem explizitem Cast. Wenn das Programm ne Nummer
größer ist, etwas komplexer und evtl. sogar von fremden Programmierern
verwendet wird, könnte es passieren - und das halte ich nicht für zu
praxisfern - daß statt einem String eben irgend ein anderer Objekt-
Typ versehentlich(!) zum Vector geaddet wird. Vielleicht aus Unacht-
samkeit. Vielleicht weil die Namen ähnlich sind. Jedenfalls funktioniert
die toString()-Variante anschließend noch, obwohl sie das nicht
sollte, weil eben kein Cast erfolgt, sondern weil toString() für
jedes Objekt exisiert.
Beispiel:
Ich habe eine Klasse XmlParser. Diese hat eine Methode "getError()".
Nun ist ein Fehler aufgetreten und ich möchte diesen loggen, will
also
fErrorLog.add(xmlParser.getError());
schreiben, schreibe aber versehentlich
fErrorLog.add(xmlParser);
stattdessen.
Später gebe ich den ErrorLog aus oder verarbeite ihn (oder wasauchimmer)
und wundere mich, wieso da nun irgendwelcher Schrott ausgegen wird.
Ich muß die Fehlerstelle suchen usw.
Würde ich einen expliziten Cast durchführen, würde ich zur Laufzeit
eine Exception an der entsprechenden Stelle erhalten, könnte dort
Debuggen (intelligente IDEs können sogar einen Breakpoint auf
beliebige Exceptions setzen) und würde sofort den Fehler bemerken.
(Zugegebenermaßen ist das Beispiel an den Haaren herbeigezogen. Aber
ich hoffe, es wurde klar was ich sagen wollte.)
Bei einem Cast würde - wie gewünscht - eine ClassCastException geworfen.
Wieso ist das erwünscht? Ob eine ClassCastException geworfen werden soll, ist eine Designentscheidung.
Richtig. Es wäre ein besserers Design wenn der Fehler nicht einfach
geschluckt wird, indem ein toString() verwendet wird, das (wie gesagt)
noch dazu langsamer ist. :-)
Entwicklung:
Ein expliziter Cast hilft, um Programmierfehler aufzudecken.
Meine Rede.
Anmmerkung: wenn der (private) Vector hingegen über eine
publizierte API (Getter/Setter) zugänglich
gemacht wird/werden soll, ist der cast zwar immer noch
performanter, verhindert aber nicht, dass irgendjemand folgendes
in seinem Code folgendes schreibt:BesagtesObjekt obj = new BesagterObjekt();
Vector v = obj.gibMirErrorLogVector();
v.add(Integer.toString(5));
Ich habe auch nicht gesagt, daß es das verhindert. Ich habe nur gesagt,
daß das Programm dadurch schlechter wartbar und langsamer wird.
Wenn du einen Setter verwendest, kann sowas natürlich nicht mehr
passieren. Hier würde sogar ein Fehler während des Kompilierens auf-
treten. -> Beste Lösung.
Ich fasse nochmal zusammen:
* Deine Lösung: Keine Fehlermeldung
* Meine Lösung: Fehlermeldung bei Laufzeit
* Mit Setter: Fehlermeldung beim Kompilieren
- Produktivversion:
[...] definitiv _keine_ ClassCastException zulassen.
Warum:
a) eine RuntimeException führt zum Programmabbruch (- sofern sie nicht abgefangen wird)
b) Das unbeabsichtigte Loggen von Informationen (irgendwelchen String-Objekten) rechtfertigt in keinem Falle einen Programmabbruch.
c) Aufgrund der Performancetests würde ich dieses dann durch einen cast mit vorangegangener Typprüfung gewährleisten.
Hier gebe ich dir in allen Punkten recht. In einer Produktversion
wäre ein toString() (bzw. die instanceof-Sache) besser, da nur
harmloser Schrott geloggt wird, dafür kein Programmabbruch erfolgt.
Dafür erfolgt während des Testens keine Exception. Der Fehler würde
damit beim Testen tendenziell weniger wahrscheinlich gefunden werden,
als wenn eine Exception auftritt.
Du erkaufst dir mit deinem toString() eine schwer auffindbare
Fehlerquelle!
Warum schwer auffindbar? Ich sollte die Stelle meines Codes kennen, an dem die Log-Infos assembliert werden.
Jein. Das trifft auf kleine Programme zu. Sobald das Projekt etwas
größer wird, weißt du das evtl. nicht mehr so ohne weiteres.
Wie oben beschrieben, bist du mit einer Exception auf jeden Fall
schneller am Ziel. Vermutlich brauchst du nicht mal einen Debugger.
Aber ich gebe dir recht. Es dürfte sich vom Aufwand nicht zu sehr
unterscheiden.
Fazit: Der Cast ist schneller und während der Entwicklung hilfreich -
auch beim Testen. toString() ist für das fertige Produkt wünschenswert,
weil damit (harmloser) Schrott geloggt wird, das Programm aber nicht
gleich stirbt. Dafür übersieht man den Fehler evtl. beim Testen.
Besser wäre natürlich die konsequente Verwendung von Properties.
Dann könnte sowas gleich gar nicht passieren.
Solange aber hier keine verwendet werden, halte ich meinen Ansatz
persönlich für besser, kann aber auch gut verstehen und nachvollziehen, wenn du deinen für besser hälst. :-)
Gruß
Slyh