Lösungsvorschlag zur Klausur zum Kurs 1618 im Sommersemester 2006 am 29.07.2006 Aufgabe 1: Objektorientierte Grundkonzepte 1 (17 Punkte) a) Erläutern Sie das Grundmodell der Objektorientierten Programmierung. (4 Punkte) Lösungsvorschlag: Ein grundlegendes Ziel der objektorientierten Programmierung ist es, eine möglichst gute softwaretechnische Modellierung der realen Welt zu unterstützen. Die objektorientierte Programmierung betrachtet eine Programmausführung hierfür als ein System kooperierender Objekte. Objekte haben einen eigenen lokalen Zustand, eine gewisse Lebensdauer und eine Identität. Objekte empfangen und bearbeiten Nachrichten. Bei der Bearbeitung von Nachrichten kann ein Objekt seinen Zustand ändern, Nachrichten an andere Objekte verschicken, neue Objekte erzeugen und existierende Objekte löschen. Objekte sind grundsätzlich selbständige Ausführungseinheiten, die unabhängig voneinander und parallel arbeiten können. 2 b) Erläutern Sie die Begriffe Klassifikation, Klasse und Vererbung, sowie deren Zusammenhang. (4 Punkte) Lösungsvorschlag: Unter Klassifikation versteht man die Zuordnung von Objekten zu Klassen, welche in hierarchischen Strukturen angeordnet sind. Klassen legen dabei die Methoden und Eigenschaften fest, die all ihren Objekten gemeinsam sind. Eigenschaften und Methoden, die mehreren Klassen gemeinsam sind, brauchen nur einmal in einer übergeordneten Klasse beschrieben zu werden und können von untergeordneten Klassen geerbt werden. Durch Vererbung werden also Codeteile von übergeordneten Klassen übernommen, ohne sie explizit, z.b. durch Copy&Paste, in die Klasse einzufügen. 2 c) Erläutern Sie die Begriffe Abstraktion und Spezialisierung im Zusammenhang mit Klassenhierarchien. (3 Punkte) Lösungsvorschlag: Unter Abstraktion versteht man das Heraussondern des unter einem bestimmten Gesichtspunkt Wesentlichen vom Unwesentlichen. Abstraktion angewandt auf eine Klasse führt zu deren Superklasse. Unter Spezialisierung versteht man das Hinzufügen von Eigenschaften oder das Verändern von Verhalten. Spezialisierung einer Klasse führt zu einer bzw. mehreren Subklassen. 2 d) Legen Sie anhand von mindestens drei Charakteristika den Unterschied zwischen Wert und Objekt dar. (3 Punkte) Lösungsvorschlag: 1. Zustand: Objekte haben einen veränderbaren Zustand. Werte hingegen sind abstrakt und können nicht verändert werden. 2. Identität: Objekte besitzen eine Identität, die vom Zustand unabhängig ist. Objekte können denselben Zustand haben, ohne identisch zu sein. Werte hingegen besitzen keine Identität. 3. Lebensdauer: Objekte besitzen eine beschränkte Lebensdauer und können erzeugt und gelöscht werden. Werte hingegen besitzen keine Lebensdauer und existieren sozusagen ewig. Lösungsvorschlag zur Klausur zum Kurs 1618 im Sommersemester 2006 am 29.07.2006 2 4. Aufenthaltsort: Objekten kann man üblicherweise einen Aufenthaltsort, beschrieben durch eine Adresse im Speicher, zuordnen. Dies ist bei Werten nicht möglich. 5. Verhalten: Objekte sind aktiv, d.h. sie reagieren auf Nachrichten. Werte hingegen sind passiv, d.h. auf ihnen operieren Funktionen. 2 e) Was versteht man unter Kapselung? (3 Punkte) Lösungsvorschlag: Als Kapselung bezeichnet man den kontrollierten Zugriff auf Methoden bzw. Attribute von Klassen. Hierfür besitzt eine Klasse eine öffentliche Schnittstelle, die darüber bestimmt, auf welche Weise mit der Klasse interagiert werden kann. Konkret heißt dies, dass der Zugriff auf interne Datenstrukturen unterbunden wird und nur noch über, in der öffentlichen Schnittstelle definierte, Methoden möglich ist. Instanzen einer Klasse können den internen Zustand von Instanzen anderer Klassen also nicht in unerwarteter Weise lesen oder ändern. Änderungen am internen Zustand einer Klasse erfolgen ausschließlich über die dafür zur Verfügung gestellten Methoden der öffentlichen Schnittstelle dieser Klasse. 2 Lösungsvorschlag zur Klausur zum Kurs 1618 im Sommersemester 2006 am 29.07.2006 3 Aufgabe 2: Fortgeschrittene objektorientierte Konzepte (18 Punkte) a) Erklären Sie die Begriffe Typ und Typhierarchie. (4 Punkte) Lösungsvorschlag: Ein Typ beschreibt bestimmte Eigenschaften der ihm zugeordneten Objekte. Für die Menge der Typen existiert eine partielle Ordnung, welche eine Hierarchie induziert. Ein Typ S ist Subtyp von einem Typ T , wenn die Objekte, die S zugeordnet sind, auch T zugeordnet werden können, d.h. die Objekte, die S zugeordnet sind, besitzen mindestens all jene Eigenschaften, die T beschreibt. 2 b) Erklären Sie das Substitutionsprinzip. (2 Punkte) Lösungsvorschlag: An allen Programmstellen, an denen ein Objekt vom Typ T zulässig ist, sind auch Objekte der Subtypen von T erlaubt. 2 c) Vergleichen Sie Subtyping mit Vererbung. (4 Punkte) Lösungsvorschlag: Für eine Klasse K1 , welche von der Klasse K2 erbt, gilt nicht notwendigerweise, dass K1 auch ein Subtyp von K2 ist. Insbesondere lassen viele Programmiersprachen für eine Klasse zwar mehrere Typen, aber nur eine Superklasse zu. Vererbung ist lediglich ein Mittel zur Wiederverwendung von Code, wohingegen Subtyping Grundlage für fortgeschrittene Konzepte wie z.B. Polymorphismus ist. 2 d) Erklären Sie die Begriffe Polymorphismus und dynamische Bindung, sowie deren Zusammenhang. (4 Punkte) Lösungsvorschlag: Polymorphismus bedeutet Vielgestaltigkeit. Im Zusammenhang mit objektorientierter Programmierung spricht man von Polymorphie, wenn Programmkonstrukte oder Programmteile für Objekte mehrerer Typen einsetzbar sind. Algorithmen können auf der Basis von allgemeinen Typen formuliert werden und müssen nicht für jeden Subtypen neu angegeben werden. Dynamische Bindung ist notwendig, um den formulierten Algorithmus auf die konkreten Objekte von Subtypen der allgemeinen Typen anzuwenden. Dabei entscheidet bei überschriebenen Methoden der Typ des tatsächlich referenzierten Objekts über die aufgerufene Methode und nicht der Typ der Variablen. 2 e) Erläutern Sie die Begriffe Kontra- und Kovarianz. (4 Punkte) Lösungsvorschlag: Die Methode eines Subtyps (methsub ) muss mindestens die aktuellen Parameter verarbeiten können, die die überschriebene Methode des Supertypes (methsuper ) verarbeiten kann, d.h. für jeden Parameter muss der Parametertyp von methsub gleich oder ein Supertyp von dem entsprechenden Typ des Parameters in methsuper sein (Kontravarianz). Die überschriebene Methode des Subtyps darf nur Ergebnisse liefern, die auch als Ergebnisse der Methode methsuper zulässig sind, d.h. der Ergebnistyp von methsub muss gleich oder ein Subtyp von methsuper sein (Kovarianz). 2 Lösungsvorschlag zur Klausur zum Kurs 1618 im Sommersemester 2006 am 29.07.2006 Aufgabe 3: Quickies in Java 4 (20 Punkte) a) Der Einstiegspunkt in ein Java-Programm ist üblicherweise die folgende Methode: public static void main( String[] args ) Erklären Sie kurz die Bestandteile public, static und void der Methodensignatur. (3 Punkte) Lösungsvorschlag: Der Bestandteil public definiert den Sichtbarkeitsbereich der Methode. In diesem Fall, also bei public, ist die Methode in allen Kontexten sichtbar, also Teil des implizit existierenden Interfaces der Klasse. Neben public existieren noch protected, private und package. Normale Datenelemente, d.h. Methoden und Felder, sind einer bestimmten Instanz der definierenden Klasse zugeordnet. Mit jeder neuen Instanz wird Speicherplatz für jedes Datenelement der neuen Instanz belegt. Ein mit static deklariertes Datenelement dagegen ist allen Instanzen einer Klasse physikalisch gemeinsam und somit gewissermaßen eine klassenglobale Variable. Man spricht daher auch von Klassenvariablen im Gegensatz zu den nicht statischen Instanzvariablen. Ändert eine Instanz den Wert eines static-Datenelements, dann macht sich diese Änderung auch bei allen anderen Instanzen derselben Klasse bemerkbar. void definiert den Typ des Rückgabewerts der Methode. An dieser Stelle sind alle der Klasse bekannten Typen, d.h. komplexe und primitive Typ, sowie void erlaubt. Der Typ des Deklarationselements, welches durch return an den Aufrufer der Methode zurückgegeben wird, muss mit dem hier definierten Typ zuweisungskompatibel sein. Der vorliegenden Fall, also bei void als Rückgabetyp, ist die einzige Ausnahme und es darf kein Wert an den Aufrufer zurückgegeben werden, d.h. die Methode darf kein returnStatement enthalten. 2 b) Erläutern Sie den Unterschied zwischen implements und extends in Java. (3 Punkte) Lösungsvorschlag: Durch Angabe von class k1 extends k2 kann eine existierende Klasse k2 durch die Klasse k1 erweitert werden. k1 erbt von k2 und ist im Falle von Java auch gleichzeitig Subtyp von k2 (Subclassing). Um Probleme mit Mehrfachvererbung zu vermeiden, kann in Java immer nur eine Klasse erweitert werden. Durch die Definition class k1 implements i1 ,i2 geht die Klasse k1 die Verpflichtung ein, für alle in den Interfaces i1 und i2 deklarierten Methoden eine Implementierung anzubieten. Die Klasse k1 ist hier Subtyp von i1 und i2 (Subtyping). Im Gegensatz zu extends können mehrere Interfaces implementiert werden. 2 c) Welche zwei grundlegend verschiedene Arten von Exceptions gibt es in Java? Diskutieren Sie für jede Art eine Ihnen bekannte Exception aus der Standardbibliothek und wie man diese Ausnahmen in einem Java Programm behandelt. (4 Punkte) Lösungsvorschlag: Als Exception bezeichnet man Ereignisse, die den normalen Programmablauf stören. In Java unterscheidet man normale Exceptions von denen, die von der Klasse RuntimeException abgeleitet sind. Im Gegensatz zu den von RuntimeException abgeleiteten Lösungsvorschlag zur Klausur zum Kurs 1618 im Sommersemester 2006 am 29.07.2006 5 Exceptions müssen die normalen Exceptions explizit behandelt werden. Ein Beispiel für eine RuntimeException ist NullPointerException, die auftritt, wenn z.b. versucht wird, den Zustand eines nicht existierenden Objekts zu ändern. Eine Beispiel für eine normale Exception ist IOException . Diese Exception tritt auf, wenn ein Fehler bei einer I/O-Operation auftritt, z.b. wenn die angeforderte Datei nicht gefunden werden kann. Exceptions können auf zwei Arten behandelt werden: Fall 1: Die Methode, in der die Exception auftreten kann, benutzt eine throws-Klausel in ihrer Methodensignatur, um die Behandlung der Exception an die aufrufende Methode zu delegieren. Fall 2: Im Methodenrumpf kann ein try/catch-Block benutzt werden, um die Exception abzuarbeiten. 2 d) Kompiliert folgendes Programm? public interface SomeInterface {} public class SomeClass implements SomeInterface {} public class Main { public void doNothing() { SomeInterface foo = new SomeClass(); SomeClass bar = foo; } } Begründen Sie ihre Antwort und korrigieren Sie gegebenenfalls das Programm. (2 Punkte) Lösungsvorschlag: Das vorgegebene Programm kompiliert nicht. Die Zuweisung bar = foo ist nicht erlaubt, d.h. die Typen SomeClass und SomeInterface sind nicht zuweisungskompatibel. Das von foo referenzierte Objekt ist zwar eine Instanz der Klasse SomeClass, der deklarierte Typ ist allerdings SomeInterface. Da SomeInterface ein Supertyp von SomeClass ist, stellen Deklarationselemente, die mit SomeInterface typisiert sind, nur einen Teil der in SomeClass definierten Methoden zur Verfügung. Um das Programm zu korrigieren, muss ein expliziter Downcast durchgeführt werden: SomeClass bar = (SomeClass) foo; 2 e) Was bedeutet das Schlüsselwort final und wo kann es überall vorkommen? (3 Punkte) Lösungsvorschlag: Wird der final-Modifier auf eine Klasse angewandt, bedeutet dies, dass von der Klasse keine Subklassen erstellt werden können. Wird der final-Modifier hingegen auf eine Variable angewandt, bedeutet dies, dass die Variable unveränderbar ist. Wird der finalModifier auf eine Methode angewandt, bedeutet dies, dass die Methode von Subklassen nicht überschrieben werden kann. 2 f) Was bedeutet der Aufruf super(..) und wo kann er vorkommen? (2 Punkte) Lösungsvorschlag: super(..) führt den Konstruktor der Superklasse aus, darf nur in Konstruktoren aufgerufen werden und muss dort die erste Anweisung sein. 2 Lösungsvorschlag zur Klausur zum Kurs 1618 im Sommersemester 2006 am 29.07.2006 g) Was bedeutet der Modifier synchronized ? 6 (3 Punkte) Lösungsvorschlag: Mit synchronized werden Programmabschnitte gekennzeichnet, die atomar ablaufen sollen. Wird z.b. die Methode void foo() { i++; } als synchronized definiert, so kann sich bei mehreren Threads, die foo() aufrufen, immer nur ein Thread im Rumpf der Methode foo() befinden. Dies bezieht sich allerdings nur auf Threads, die foo() für dasselbe Objekt aufrufen. Zwei verschiedene Threads können durchaus parallel die Methode foo() für unterschiedliche Objekte ausführen. 2 Lösungsvorschlag zur Klausur zum Kurs 1618 im Sommersemester 2006 am 29.07.2006 Aufgabe 4: Vererbung und Polymorphismus 7 (10 Punkte) a) Gegeben sei die folgende Klasse Auto: public class Auto { protected double neuwagenpreis; protected int baujahr; protected String modell; public Auto(double neuwagenpreis, int baujahr, String modell) { this.neuwagenpreis = neuwagenpreis; this.baujahr = baujahr; this.modell = modell; } public double getPreis() { return neuwagenpreis; } } Erweitern Sie diese Klasse so, dass eine neue Klasse GebrauchtAuto entsteht, die zusätzlich die bereits gefahrenen Kilometer speichert. Schreiben Sie den dazu notwendigen Konstruktor und überschreiben Sie die Methode double getPreis() so, dass nach jeweils 20000 gefahrenen Kilometern der Neuwagenpreis des Wagens um 10% sinkt. Beachten Sie, dass der Händler einen Mindestpreis von 2000 für jeden Gebrauchtwagen angesetzt hat. (6 Punkte) Lösungsvorschlag: public class GebrauchtAuto extends Auto { protected int gefahreneKilometer; public GebrauchtAuto(double neuwagenpreis, int baujahr, String modell, int gefahreneKilometer) { super(neuwagenpreis, baujahr, modell); this.gefahreneKilometer = gefahreneKilometer; } public double getPreis() { double gebrauchtWagenPreis=0; gebrauchtWagenPreis = neuwagenpreis - ((gefahreneKilometer/20000)*(neuwagenpreis/10.0)); if (gebrauchtWagenPreis < 2000.0) return 2000.0; else return gebrauchtWagenPreis; } } 2 b) Gegeben sei nun die folgende main()-Methode: public class AutoHaendler { public static void main ( String[] args ) { GebrauchtAuto auto1 = new GebrauchtAuto(20000, 2002, "Audi TT", 70000); System.out.println("Preis:"+ auto1.getPreis()); Lösungsvorschlag zur Klausur zum Kurs 1618 im Sommersemester 2006 am 29.07.2006 8 Auto auto2 = auto1; System.out.println("Preis:"+ auto2.getPreis()); } } Geben Sie an, welche Methoden-Implementierungen bei auto1.getPreis() und bei auto2.getPreis() aufgerufen werden und wie die Ausgabe aussieht. Begründen Sie Ihre Antwort. (4 Punkte) Lösungsvorschlag: In beiden Fällen wird der korrekte Preis des Gebrauchtwagens, also 14000, ausgegeben. Beim Methodenaufruf von auto1.getPreis() ist die Sachlage eindeutig. Der Typ von auto1 ist als GebrauchtAuto deklariert und das referenzierte Objekt entspricht genau diesem Typ. Deshalb wird die in GebrauchtAuto überschriebene Methode double getPreis() aufgerufen. Beim zweiten Aufruf ist die Sachlage hingegen anders. Die Variable auto2 ist mit Auto typisiert, zeigt aber auf ein Objekt vom Typ GebrauchtAuto. Bei überschriebenen Methoden entscheidet der Typ des referenzierten Objekts über die aufgerufene Methode und nicht der Typ der Variable. Deswegen wird auch beim zweiten Aufruf die Methode double getPreis() aus der Klasse GebrauchtAuto aufgerufen. 2 Lösungsvorschlag zur Klausur zum Kurs 1618 im Sommersemester 2006 am 29.07.2006 Aufgabe 5: Rekursive Programmierung 9 (15 Punkte) a) Der binäre Baum ist eine besondere Baumstruktur. Er definiert sich dadurch, dass jeder Knoten (= Element des Baums) maximal zwei Nachfolger haben darf. Beim binären Baum unterscheidet man zwischen der Wurzel, den inneren und äußeren Knoten. Die Wurzel ist der Grundknoten des Baumes, da sie keinen Vorgänger besitzt. Wenn der Zeiger auf die Wurzel leer ist, ist der Baum leer. Äußere Knoten nennt man jene, welche keine Nachfolger haben. Diese Knoten befinden sich auf der untersten Ebene eines Baumes und die Referenzen auf den linken und rechten Nachfolgeknoten sind bei diesen Knoten leer. (10 Punkte) class Binaerbaum { protected Knoten wurzel=null; protected class Knoten { Element wert; Knoten links, rechts; Knoten (Element wert, Knoten links, Knoten rechts) { this.wert = wert; this.links = links; this.rechts = rechts; } public String toString() { return wert.getValue(); } } public boolean isEmpty() { return (wurzel==null); } } class Element { private int value; Element(int value) { this.value = value; } public int compareTo(Element e) { if ( e.getValue() < this.value) { return 1; } else if ( e.getValue() > this.value) { return -1; } else { return 0; } } public int getValue() { return value; } } Implementieren Sie eine Methode public boolean insert(Element e), die Elemente in den Baum einfügt. Eine Instanz e1 der Klasse Element wird als neuer Knoten im linken Teilbaum eines bestehenden Knotens, welcher eine Instanz e2 der Klasse Element beinhaltet, eingefügt, wenn e1 .getV alue() < e2 .getV alue(). Im Falle von e1 .getV alue() > e2 .getV alue() wird das Element e1 im rechten Teilbaum eingefügt. Lösungsvorschlag zur Klausur zum Kurs 1618 im Sommersemester 2006 am 29.07.2006 10 Herrscht Gleichheit, so soll die Funktion false zurückliefern. Als Grundlage benutzen Sie obigen Code. Lösungsvorschlag: public boolean insert(Element e) { if(this.isEmpty()) { wurzel = new Knoten(e,null,null); return true; } else { return insertSubTree(e, wurzel); } } private boolean insertSubTree(Element e, Knoten k) { if (k.wert.compareTo(e) > 0) { if(k.links == null) { k.links = new Knoten(e,null,null); return true; } else { return insertSubTree(e,k.links); } } else if (k.wert.compareTo(e) < 0) { if(k.rechts == null) { k.rechts = new Knoten(e,null,null); return true; } else { return insertSubTree(e,k.rechts); } } else { return false; } } 2 b) Ein binärer Baum kann auf verschiedene Arten durchlaufen werden, so dass jeder Knoten genau einmal besucht wird. Die wohl wichtigste Durchlaufart ist die sogenannte InorderTraversierung, welche folgender Definition folgt: Für jeden Knoten k: • Besuche den linken Teilbaum des Knotens k. • Besuche k. • Besuche den rechten Teilbaum des Knotens k. Implementieren Sie auf Basis des obigen Codes eine Methode public String printInorder(), die die im Binärbaum gespeicherten Werte als String in einer Inorder-Reihenfolge zurückliefert. (5 Punkte) Lösungsvorschlag: Lösungsvorschlag zur Klausur zum Kurs 1618 im Sommersemester 2006 am 29.07.2006 11 public String printInorder() { String order ="Inorder: "; if (wurzel == null) return "Empty Tree"; else return order+=inorderString(wurzel); } private String inorderString(Knoten k) { if (k==null) return ""; else { return "["+inorderString(k.links)+"]"+k+"["+inorderString(k.rechts)+"]"; } } 2 Lösungsvorschlag zur Klausur zum Kurs 1618 im Sommersemester 2006 am 29.07.2006 Aufgabe 6: I/O mit Java 12 (10 Punkte) Schreiben Sie ein Java-Programm, um zu bestimmen, ob zwei aufeinander folgende, per Tastatur eingegebene Zeilen Palindrome voneinander darstellen, d.h. dieselben Zeichen in umgekehrter Reihenfolge enthalten. Groß- und Kleinschreibung soll hierbei ignoriert werden. Nachdem das Ergebnis ausgegeben wurde, beendet sich das Programm. Bekannte Beispiele für Palindrome sind Otto“ oder Rentner“. ” ” Hinweis: Für diese Aufgabe stehen Ihnen insbesondere die im Anhang aufgeführten Klassen zur Verfügung. Natürlich können Sie auch andere Ihnen bekannte Klassen aus dem JDK benutzen. Lösungsvorschlag: import java.io.*; public class PalindromCheck { static final BufferedReader in=new BufferedReader (new InputStreamReader(System.in)); static final String prompt=new String("Bitte Zeichenkette Eins eingeben> "); static final String prompt2=new String("Bitte Zeichenkette Zwei eingeben> "); static final String positivPalindrom=new String("Die beiden Zeichenketten sind ein Palindrom."); static final String negativPalindrom=new String("Die beiden Zeichenketten sind kein Palindrom."); static final String lengthMismatch=new String("Bitte geben Sie zwei gleichlange Zeichenketten ein."); static final String byebye=new String("Auf Wiedersehen!"); static boolean isPalindrom=true; public static void main(String args[]) throws IOException { System.out.println(prompt); String lineOne=in.readLine(); System.out.println(prompt2); String lineTwo=in.readLine(); if(lineOne.length() != lineTwo.length()) { System.out.println(lengthMismatch); } else { for(int i=0;i<lineOne.length();i++) { if(lineOne.toUpperCase().charAt(i) != lineTwo.toUpperCase().charAt(lineTwo.length() - i -1)) { isPalindrom = false; } } } if(isPalindrom) { System.out.println(positivPalindrom); } else { System.out.println(negativPalindrom); } System.out.println(byebye); in.close(); } } 2 Lösungsvorschlag zur Klausur zum Kurs 1618 im Sommersemester 2006 am 29.07.2006 Aufgabe 7: Sortieren mit Java 13 (10 Punkte) Schreiben Sie ein Java-Programm, welches eine Datei einliest, die Zeilen in aufsteigender Reihenfolge sortiert und in eine neue Datei schreibt. Ein- und Ausgabedatei werden als Parameter an das Programm übergeben. Benutzen Sie eine Klasse Line die das Verwalten von eingelesenen Zeilen übernimmt, d.h. eine Instanz dieser Klasse speichert eine Zeile der Eingabedatei. Die Klasse Line soll das Interface Comparable implementieren, also die Methode int compareTo(Object l) zur Verfügung stellen. Seien l1 und l2 Instanzen der Klasse Line, so muss die Methode int compareTo(Object l), aufgerufen durch l1 .compareT o(l2 ), folgendes Verhalten zeigen. Groß- und Kleinschreibung soll hierbei ignoriert werden. a) Seien l1 und l2 gleichlang, dann ist l1 größer als l2 , wenn das erste Zeichen von l1 , in dem sich l1 von l2 unterscheidet, weiter hinten im Alphabet zu finden ist. b) Seien l1 und l2 gleichlang, dann ist l1 kleiner als l2 , wenn das erste Zeichen von l1 , in dem sich l1 von l2 unterscheidet, weiter vorne im Alphabet zu finden ist. c) Seien l1 und l2 gleichlang, dann ist l1 gleich l2 , wenn sich l1 und l2 nicht voneinander unterscheiden. d) Sei l1 länger als l2 , dann ist l1 größer als l2 , wenn das erste Zeichen von l1 , in dem sich l1 von l2 unterscheidet, weiter hinten im Alphabet zu finden ist oder l2 vollständig als Präfix in l1 enthalten ist. Andernfalls ist l1 kleiner als l2 . e) Sei l1 kürzer als l2 , dann ist l1 größer als l2 , wenn das erste Zeichen von l1 , in dem sich l1 von l2 unterscheidet, weiter hinten im Alphabet zu finden ist. Andernfalls ist l1 kleiner als l2 . Beispielsweise gilt Abc < aBd, Aaa < aaaa oder aaaabaaa < aaaac. Hinweis: Für diese Aufgabe stehen Ihnen insbesondere die im Anhang aufgeführten Klassen zur Verfügung. Natürlich können Sie auch andere Ihnen bekannte Klassen aus dem JDK benutzen. Lösungsvorschlag: import import import import import java.util.Collections; java.util.Iterator; java.util.List; java.io.*; java.util.LinkedList; public class FileSorter { public static void main(String[] args) { try { String line; List lines = new LinkedList(); File input = new File(args[0]); File output = new File(args[1]); FileReader inputStream = new FileReader(input,false); FileWriter outputStream = new FileWriter(output,true); Lösungsvorschlag zur Klausur zum Kurs 1618 im Sommersemester 2006 am 29.07.2006 14 BufferedReader inputReader = new BufferedReader(inputStream); //Zeilenweise einlesen aus der Eingabedatei und in die List lines speichern while ((line = inputReader.readLine()) != null) { Line newLine = new Line(line); lines.add(newLine); } //Wir benutzen die Sortiermethode der Klasse Collections. //Hierfür müssen die Elemente der zu sortierenden Collection die Methode //compareTo(Object o) implementieren. Collections.sort(lines); //Ausgeben der sortierten Liste in die Ausgabedatei Iterator it = lines.iterator(); while (it.hasNext()) { String tmpLine = ((Line)it.next()).toString() + "\n"; outputStream.write(tmpLine); } inputStream.close(); outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } public class Line implements Comparable { String line; public Line(String line) { this.line = line; } public void setLine(String line) { this.line = line; } public String getLine() { return line; } public int compareTo(Object l) { //Sicherstellen das wir mit einer Instanz von Line verglichen werden if(l instanceof Line) { //Groß- und Kleinschreibung wird durch //die Aufrufe von toUpperCase() ignoriert. String l1=line.toUpperCase(); String l2=((Line)l).getLine().toUpperCase(); // Ermittlung der kürzeren Zeichenkette. Damit wird Fall d) obsolete. int length = l1.length() > l2.length() ? l2.length() : l1.length(); //Zeichenweise die Zeichenkentten vergleichen. for(int i=0;i<length;i++) { //l1>l2 wenn ein Zeichen von l1 größer (weiter hinten im Alphabet) als das dazugehörige //Zeichen von l2 ist. Für alle Zeichen davor herrscht Gleichheit. if(l1.charAt(i) > l2.charAt(i)) { return 1; Lösungsvorschlag zur Klausur zum Kurs 1618 im Sommersemester 2006 am 29.07.2006 15 //l1<l2 wenn ein Zeichen von l1 kleiner (weiter vorne im Alphabet) als das dazugehörige //Zeichen von l2 ist. Für alle Zeichen davor herrscht Gleichheit. } else if(l1.charAt(i) < l2.charAt(i)) { return -1; } } if(l1.length() > l2.length()) { return -1; } else if (l1.length() < l2.length()) { return 1; } else { return 0; } } return 0; } public String toString() { return line; } } 2 Lösungsvorschlag zur Klausur zum Kurs 1618 im Sommersemester 2006 am 29.07.2006 16 ANHANG package java.io; public class BufferedReader extends java.io.Reader { //Erzeugt einen neuen BufferedReader, der seine Daten aus ’in’ bezieht. //Der Puffer hat eine voreingestellte Größe von 8.192 Zeichen. public BufferedReader(Reader in); //Schließt den Stream. //Exception: IOException public void close(); //Liest ein Zeichen und liefert es in der Unicode-Codierung zurück. //Der Rückgabewert ist -1, falls das Ende des Streams erreicht ist. //Exception: IOException public int read(); //Liest eine Textzeile aus dem Stream. Als Zeilenende-Sequenz //wird ’\r’, ’\n’ (UNIX) oder "\r\n" (Windows) akzeptiert. public String readLine(); } package java.io; public class InputStreamReader extends java.io.Reader { //Erzeugt einen neuen InputStreamReader, der die aus ’in’ gelesenen Bytes nach der //standardmäßigen Codierung der Plattform in Unicode-Zeichen konvertiert. public InputStreamReader(InputStream in); } package java.lang; public interface Comparable { //Vergleicht dieses Objekt mit ’o’. Der Rückgabewert ist kleiner als null, null oder größer als null, //je nachdem, ob dieses Objekt im Sinne der Ordnung kleiner, gleich oder größer als o ist. //Die Implementierung muss Asymmetrie und Transitivität sicherstellen, d. h.: //* Wenn a < b, dann nicht a > b //* Wenn a < b und b < c, dann auch a < c. public int compareTo(Object o); } package java.lang; public final class String { //Liefert das Zeichen aus dem String, das an der Position index steht. //’index’ wird hierbei von Null an gezählt. public char charAt(int index); //Liefert die Länge des Strings. public int length(); //Liefert einen neuen String, in dem alle Großbuchstaben gemäß den //länderspezifischen Einstellungen in Kleinbuchstaben umgewandelt sind. public String toLowerCase(); //Liefert einen neuen String, in dem alle Kleinbuchstaben gemäß den //länderspezifischen Einstellungen in Großbuchstaben umgewandelt sind. public String toUpperCase(); } Lösungsvorschlag zur Klausur zum Kurs 1618 im Sommersemester 2006 am 29.07.2006 17 package java.io; public class File { // Erzeugt eine neue Instanz der Klasse File, mit der Datei, die unter ’pathname’ zu finden ist. public File(String pathname); } package java.io; public class FileWriter { //Erzeugt eine neue Instanz der Klasse FileWriter, die in die über ’fileName’ referenzierte Datei schreibt. //’append’ gibt an, ob Daten an die bestehende Datei angehängt werden sollen //oder ob die Datei überschrieben wird FileWriter(String fileName, boolean append); } package java.io; public class FileReader { //Erzeugt eine neue Instanz der Klasse FileReader, die aus der in ’file’ referenzierten Datei liest. FileReader(File file); } package java.util; public class Collections { //Sortiert die Einträge in der Liste list. Dazu wird die compareTo-Methode benutzt. public static void sort(List list); }