Lösung 10

Transcrição

Lösung 10
Ludwig-Maximilians-Universität München
Institut für Informatik
Prof. Dr. Christian Böhm
Annahita Oswald, Bianca Wackersreuther
München, 08.01.2010
Einführung in die Programmierung
WS 2009/10
Übungsblatt 10: Mehr zu Objektorientierung, Arbeiten mit Strings
Besprechung: 18./20./21./22.01.2010
Ende der Abgabefrist: Montag, 18.01.2009 10:00 Uhr.
Hinweise zur Abgabe:
Geben Sie bitte Ihre gesammelten Lösungen zu diesem Übungsblatt in einer Datei loesung10.zip unter
http://www.pst.ifi.lmu.de/uniworx/ ab.
Bitte beachten Sie, dass die Aufgabe 10-3 nicht in die Bonusregelung eingeht. Bereiten Sie diese aber bitte
trotzdem vor, damit Sie der Übung optimal folgen können.
Aufgabe 10-1
10 Punkte
Objektreferenz und null
Die Datei Text.java hat folgenden Inhalt:
public class Text {
public static void main(String[] args) {
String text;
// *1*
text = null;
// *2*
text = "";
// *3*
text = "ABC";
// *4*
}
}
Erklären Sie jeweils kurz (2-3 Sätze), welchen Wert der Ausdruck text.length() an den mit Kommentaren
markierten Stellen *1* bis *4* jeweils hat. Beschreiben Sie dabei auch, warum gerade dieser Wert zustande
kommt.
Schreiben Sie Ihre Antwort in eine Datei text.txt und geben Sie diese ab.
1
Lösungsvorschlag:
Wir betrachten folgenden Analogie: Der Anrufbeantworter von Xaver kann nur eine Nachricht speichern und
wir wollen wissen, aus wie vielen Wörten sie besteht.
1. Der Ausdruck text.length() hat an dieser Stelle keinen Wert. Er ist an dieser Stelle in einem JavaProgramm nicht einmal syntaktisch erlaubt, weil die Variable text an dieser Stelle keinen Wert hat. Er
verursacht folgenden Fehler zur Übersetzungszeit:
Text.java:6: variable text might not have been initialized
System.out.println(text.length());
ˆ
1 error
Analogie: Xaver hat überhaupt keinen Anrufbeantworter.
2. Der Ausdruck text.length() hat an dieser Stelle keinen Wert. Der Wert von text ist null und das ist
keine Referenz auf ein Objekt, also ist auch kein Methodenaufruf dafür definiert. Er verursacht folgende
Ausnahme zur Laufzeit:
Exception in thread "main" java.lang.NullPointerException
at Text.main(Text.java:9)
Analogie: Xaver hat zwar einen Anrufbeantworter, der jedoch keine Stromversorgung besitzt und daher
keine Funktionen ausführen kann.
3. Der Ausdruck text.length() hat an dieser Stelle den Wert 0. Der Wert von text ist der leere String
und die Methode length() ist dafür definiert und liefert 0.
Analogie: Auf dem Anrufbeantworter von Xaver gibt es eine Nachricht von einem Anrufer, der gar nichts
gesagt hat.
4. Der Ausdruck text.length() hat an dieser Stelle den Wert 3. Der Wert von text ist ein String von
drei Zeichen und die Methode length() ist dafür definiert und liefert 3.
Analogie: Auf dem Anrufbeantworter von Xaver gibt es eine Nachricht von einem Anrufer, der gesagt
hat: “Bitte daheim anrufen!”
Hinweis: Achten Sie besonders auf den Unterschied zwischen 1. und 2. sowie zwischen 2. und 3.!
2
Aufgabe 10-2
10 Punkte
Objektorientierte Modellierung
Bei Quadern handelt es sich um Objekte, deren Kanten achsenparallel in einem dreidimensionalen Koordinatensystem liegen. Quader werden spezifiziert (siehe Bild) durch einen der Eckpunkte p und die Länge der an p
angrenzenden Kanten x (parallel zur x-Achse), y (parallel zur y-Achse) und z (parallel zur z-Achse).
z
y
p
x
Hinweis: Achten Sie im Folgenden in der gesamten Aufgabe auf sinnvolle Datenkapselung.
(a) Implementieren Sie eine Klasse Punkt3D zur Verwaltung dreidimensionaler Punkte in einem reellen
Koordinatensystem als Erweiterung der Klasse Punkt2D, die Sie auf der Vorlesungs-Homepage finden:
Definieren Sie neben einem geeigneten Konstruktor und geeigneten Attributen die folgende Methode:
void verschiebe(double x, double y, double z),
die den Punkt um x entlang der x-Achse, um y entlang der y-Achse und um z entlang der z-Achse
verschiebt.
Lösungsvorschlag:
public class Punkt3D extends Punkt2D {
private double zKoordinate;
public Punkt3D(double x, double y, double z) {
super(x, y);
this.zKoordinate = z;
}
public void verschiebe(double x, double y, double z) {
super.verschiebe(x, y);
this.zKoordinate += z;
}
public double getZKoordinate() {
return this.zKoordinate;
}
}
3
(b) Implementieren Sie eine Klasse Quader, die die Klasse Punkt3D aus der vorherigen Teilaufgabe sinnvoll verwendet. Die Klasse soll einen geeigneten Konstruktor und geeignete Attribute bereitstellen. Implementieren Sie für die Klasse Quader zusätzlich folgende Methode:
void verschiebe(double x, double y, double z),
die den Quader um x entlang der x-Achse, um y entlang der y-Achse und um z entlang der z-Achse
verschiebt.
Lösungsvorschlag:
public class Quader {
private Punkt3D eckPunkt;
private double laengeX;
private double laengeY;
private double laengeZ;
public Quader(Punkt3D eckPunkt, double x, double y, double z) {
this.eckPunkt = new Punkt3D(eckPunkt.getXKoordinate(), eckPunkt
.getYKoordinate(), eckPunkt.getZKoordinate());
this.laengeX = x;
this.laengeY = y;
this.laengeZ = z;
}
public void verschiebe(double x, double y, double z) {
this.eckPunkt.verschiebe(x, y, z);
}
}
4
Aufgabe 10-3
String vs. StringBuilder/StringBuffer
0 Punkte
Als angehende Akademiker sollten Sie stets bereit sein, die Äußerungen Ihres Dozenten zu hinterfragen. Kann
es wirklich sein, dass die Konkatenation von Strings problematisch ist? Das ist doch eine Operation, die ständig
vorkommt und sogar mit einem eigenen Operator unterstützt wird!
Zur Überprüfung der Effizienz von String-Konkatenationen gegenüber der Verwendung von StringBuilder
und StringBuffer schreiben Sie in einer Klasse StringEffizienz drei Methoden
• public static String buildString(int n)
• public static String buildStringBuilder(int n)
• public static String buildStringBuffer(int n)
die jeweils einen String zurückgeben, der die Zahl 10n für n ≥ 0 darstellt, also einen String bestehend aus
dem Zeichen ’1’ n-mal gefolgt von dem Zeichen ’0’. Die n-fache Wiederholung soll in jeder der drei Methoden gleichartig als for-Schleife implementiert sein, nur wird der String in den verschiedenen Methoden
unterschiedlich gebildet:
• buildString verwendet fortgesetzte String-Konkatenation
• buildStringBuilder verwendet einen StringBuilder, um die Zeichenkette aufzubauen
• buildStringBuffer verwendet einen StringBuffer, um die Zeichenkette aufzubauen
Implementieren Sie nun in der main-Methode der Klasse einen Effizienz-Test zur Untersuchung des Laufzeitverhaltens der Methoden. Dazu können Sie sich natürlich an der Klasse Laufzeit (Übungsblatt 8, Aufgabe 2)
orientieren. Pro Durchlauf der Klasse StringEffizienz soll jeweils nur eine der drei Methoden getestet
werden, d.h. in der main-Methode darf jeweils nur eine Methode aufgerufen werden. Sie können dies z.B.
durch Verwendung unterschiedlicher Kommandozeilenargumente für die einzelnen Methoden realisieren:
Beispiel:
Der Aufruf
java StringEffizienz -string
führt in der main-Methode zu einem Aufruf der Methode buildString,
der Aufruf
java StringEffizienz -builder
führt in der main-Methode zu einem Aufruf der Methode buildStringBuilder.
5
Lösungsvorschlag:
/**
* Klasse zum Effizienz-Vergleich zwischen String-Konkatenation, StringBuilder
* und StringBuffer.
*/
public class StringEffizienz {
/**
* Erzeugt einen String, der die Zahl 10<sup>n</sup> darstellt, also eine
* 1 gefolgt von <code>n</code> Nullen. Dieser String wird durch iterierte
* String-Konkatenation erstellt.
*
* @param n
die gew&uuml;nschte (nicht-negative) Anzahl der Nullen
*
@return
die
Zahl 10<sup>n</sup>
*
/
*
public static String buildString(int n) {
String s = "1";
for (int i = 1; i <= n; i++) {
s += "0";
}
return s;
}
/**
* Erzeugt einen String, der die Zahl 10<sup>n</sup> darstellt, also eine
* 1 gefolgt von <code>n</code> Nullen. Dieser String wird durch einen
* StringBuilder erstellt.
*
* @param n
die gew&uuml;nschte (nicht-negative) Anzahl der Nullen
*
@return
die
Zahl 10<sup>n</sup>
*
/
*
public static String buildStringBuilder(int n) {
StringBuilder s = new StringBuilder("1");
for (int i = 1; i <= n; i++) {
s.append("0");
}
return s.toString();
}
/**
* Erzeugt einen String, der die Zahl 10<sup>n</sup> darstellt, also eine
* 1 gefolgt von <code>n</code> Nullen. Dieser String wird durch einen
* StringBuffer erstellt.
*
* @param n
die gew&uuml;nschte (nicht-negative) Anzahl der Nullen
*
* @return die Zahl 10<sup>n</sup>
*/
public static String buildStringBuffer(int n) {
StringBuffer s = new StringBuffer("1");
for (int i = 1; i <= n; i++) {
s.append("0");
}
return s.toString();
}
6
/**
* Gibt die Zeitmessung f&uuml;r die String-Konkatenation durch die Methode
* {@link #buildString(int) buildString(number)} aus.
*
* @param number
gew&uuml;nschte L&auml;nge des Strings
*
*/
public static void printStringTest(int number) {
System.out.print(number);
long stringStart = System.currentTimeMillis();
buildString(number);
long stringStop = System.currentTimeMillis();
System.out.println(" " + (stringStop - stringStart));
}
/**
* Gibt die Zeitmessung f&uuml;r die String-Konkatenation durch die Methode
* {@link #buildStringBuffer(int) buildStringBuffer(number)} aus.
*
* @param number
gew&uuml;nschte L&auml;nge des Strings
*
*/
public static void printStringBufferTest(int number) {
System.out.print(number);
long stringBufferStart = System.currentTimeMillis();
buildStringBuilder(number);
long stringBufferStop = System.currentTimeMillis();
System.out.println(" " + (stringBufferStop - stringBufferStart));
}
/**
* Gibt die Zeitmessung f&uuml;r die String-Konkatenation durch die Methode
* {@link #buildStringBuilder(int) buildStringBuilder(number)} aus.
*
* @param number
gew&uuml;nschte L&auml;nge des Strings
*
/
*
public static void printStringBuilderTest(int number) {
System.out.print(number);
long stringBuilderStart = System.currentTimeMillis();
buildStringBuilder(number);
long stringBuilderStop = System.currentTimeMillis();
System.out.println(" " + (stringBuilderStop - stringBuilderStart));
}
7
/**
* F&uuml;hrt eine Messreihe f&uuml;r
* {@link #printStringTest(int) printStringTest(n)},
* {@link #printStringBufferTest(int) printStringBufferTest(n)} oder
* {@link #printStringBuilderTest(int) printStringBuilderTest(n)} durch, mit
* <code>n=0,...,10000</code>, mit Schrittweite 100 hochgez&auml;hlt.
* Die Auswahl der zu testenden Methode erfolgt &uuml;ber die
* Kommandozeilenparameter.
*
* @param args
Kommandozeilenparameter: -string, -buffer oder -builder
*
*/
public static void main(String[] args) {
if (args.length == 0) {
System.out.println("M\u00F6gliche Optionen: -string, -buffer,"
+ " -builder.\nKeine Option gegeben, Programm wird"
+ " beendet.");
return;
}
String option = args[0];
final String STRING = "-string";
final String BUFFER = "-buffer";
final String BUILDER = "-builder";
// kleinster Wert fuer die Laenge des Strings
final int START = 0;
// groesster Wert fuer die Laenge des Strings
final int STOP = 10000;
// Schrittweite zur Vergroesserung der Laenge des Strings
final int SCHRITT = 100;
if (option.equals(STRING)) {
System.out.println("Testlauf f\u00FCr Methode buildString ");
System.out.println("#Laenge Zeit");
for (int i = START; i <= STOP; i += SCHRITT) {
printStringTest(i);
}
} else if (option.equals(BUFFER)) {
System.out.println("Testlauf f\u00FCr Methode buildStringBuffer");
System.out.println("#Laenge Zeit");
for (int i = START; i <= STOP; i += SCHRITT) {
printStringBufferTest(i);
}
} else if (option.equals(BUILDER)) {
System.out.println("Testlauf f\u00FCr Methode buildStringBuilder");
System.out.println("#Laenge Zeit");
for (int i = START; i <= STOP; i += SCHRITT) {
printStringBuilderTest(i);
}
} else {
System.out.println("Ung\u00FCltige Option.");
}
}
}
8
Was beobachten Sie?
Lösungsvorschlag:
Wir beobachten folgende Werte:
Die Zunahme im Zeitverbrauch der String-Konkatenation steigt monoton und ziemlich schnell, während im
Bereich unserer Skala der Zeitverbrauch von StringBuilder und StringBuffer konstant (und ununterscheidbar)
bleibt. Die JVM bietet eine Option an, um dem Garbage Collector ausführliche Ausgaben zu gestatten:
java -verbose:gc StringEffizienz
Die Verwendung dieser Option wird natürlich die Laufzeit beeinflussen, ist aber unabhängig von der Laufzeitevaluation aufschlussreich: Man sieht dann, dass innerhalb der Methode buildString, die String-Konkatenation
verwendet, sehr oft Garbage Collection betrieben wird. Der Speicherplatz ist auch rasch ziemlich stark verbraucht. Während der beiden anderen Methoden ist Garbage Collection anscheinend nicht nötig. Die Garbage
Collection nimmt selbst sicherlich auch einiges von der Laufzeit in Anspruch, entscheidend ist aber wohl die
ständige fortgesetzte Allokation von neuem Speicherplatz für stets neue String-Objekte durch die Methode
buildString.
Bemerkung zu Speicher- (und dadurch auch Zeit-)effizientem Verhalten von StringBuilder und StringBuffer:
Beide Klassen erben dieses Verhalten vom AbstractStringBuilder, in dem es schon vollständig modelliert ist): Die wachsende Sequenz von Zeichen wird in einem char-Array gehalten. Wenn dieses zu klein wird,
wird es verdoppelt und der Inhalt des alten Arrays in das neue kopiert. In diesen Momenten verwaist also eine
Referenz auf dem Heap (das alte Array).
Warum ist das aber nicht schlimm? Da die Länge jedesmal verdoppelt wird, kommt dies mit zunehmender
Länge n der Sequenz nur O(log n) mal vor. Übrigens kann man die gewünschte (Start-)Kapazität auch beim
Konstruktor angeben.
9