© Holger Röder Winter 2008/2009 Programmentwicklung se Java: Kapitel 5 Die Java-Standardbibliotheken Programmentwicklung WS 2008/2009 Holger Röder [email protected] © Holger Röder Winter 2008/2009 Programmentwicklung se Überblick über Kapitel 5 Mathematik Strings Die Klasse System Datum und Zeit Internationalisierung/Lokalisierung Datums- und Zahlenformatierung Formatierung à la printf Eingabe und Ausgabe Character- und Byte-Streams, Wahlfreier Zugriff Dateien und Verzeichnisse Serialisierung Threads Datenstrukturen Collection und Map Sortierung 2 © Holger Röder Programmentwicklung Winter 2008/2009 Überblick über die Java SE se Quelle: http://java.sun.com/javase/6/docs/ 3 © Holger Röder Winter 2008/2009 Programmentwicklung se Mathematik mit java.lang.Math Die Klasse Math enthält statische mathematische Funktionen, vor allem zur Gleitkommarechnung: Winkelberechnung: sin(), cos(), tan() etc. Exponentialrechnung, Logarithmus: exp(), pow(), log(), log10(), sqrt() Minimum, Maximum: min(), max() Runden, Abschneiden: ceil(), floor(), round(), abs() double double double double double a = -17.51; x1 = Math.ceil(a); x2 = Math.floor(a); x3 = Math.round(a); x4 = Math.abs(a); // -17.0 // -18.0 // -18.0 // 17.51 4 © Holger Röder Winter 2008/2009 Programmentwicklung se Strings Die Klasse String repräsentiert Zeichenketten in Java. Sie bietet eine Vielzahl von Methoden zur Manipulation und zur Ermittlung bestimmter Eigenschaften von Zeichenketten. String-Zeichenketten sind – einmal festgelegt – unveränderlich: Länge und Inhalt bleiben konstant. String s = "Hallo Welt"; // -> String-Objekt erzeugt s = s.substring(6); // "Welt" -> neues, zweites String-Objekt erzeugt Dynamische Zeichenketten werden von der Klasse StringBuilder implementiert. Diese bietet Methoden zum Einfügen: append(), insert() Löschen: deleteCharAt(), delete() und Verändern: setCharAt(), replace() von StringBuilder-Zeichenketten. Die Methoden erzeugen keine neuen Objekte, sondern manipulieren eine einzige Instanz der Zeichenkette im Speicher. 5 © Holger Röder Winter 2008/2009 Programmentwicklung se String vs. StringBuilder Zum Beispiel bei der vielfachen Konkatenation von Zeichenketten machen sich die Unterschiede zwischen String und StringBuilder bemerkbar. String s = new String(); Date t1 = new Date(); for (int i = 0; i < 20000; i++) { s += 'x'; // String-Konkatenation } Date t2 = new Date(); System.out.println("Laufzeit String: " + (t2.getTime() - t1.getTime()) + "ms"); StringBuilder sb = new StringBuilder(); Date t3 = new Date(); for (int i = 0; i < 20000; i++) { sb.append('x'); // StringBuilder-Konkatenation } Laufzeit String: 547ms Date t4 = new Date(); Laufzeit StringBuilder: 0ms System.out.println("Laufzeit StringBuilder: " + (t4.getTime() - t3.getTime()) + "ms"); 6 © Holger Röder Winter 2008/2009 Programmentwicklung se String zerlegen Mit der Methode String.split() kann ein String in Teil-Strings zerlegt werden. Die Trennzeichen sind frei wählbar und werden als regulärer Ausdruck angegeben. Einfaches Beispiel: Komma-Trennung: String laenderliste = "Frankreich,Schweiz,Deutschland,Österreich"; String[] laender = laenderliste.split(","); Frankreich for (String land: laender) { Schweiz System.out.println(land); Deutschland } Österreich Beispiel mit regulärem Ausdruck: mehrere Trennzeichen String laenderliste = "Frankreich, Schweiz;Deutschland; Österreich"; String[] laender = laenderliste.split("(,|;) *"); Frankreich ... Schweiz Deutschland Österreich „Komma oder Semikolon, jeweils gefolgt von beliebig vielen Leerzeichen.“ 7 © Holger Röder Die Klasse System bietet einige nützliche statische Methoden, z. B. für den Zugriff auf Properties (vom Laufzeitsystem zur Verfügung gestellte Eigenschaften): System.getProperty("os.name"); // bspw. Windows XP Weitere Schlüssel: java.version, java.class.path, os.version, user.name, user.home, file.separator, ... System.exit(int status) beendet das Programm mit dem angegebenen Rückgabewert. System.gc() startet die Garbage Collection. Programmentwicklung Winter 2008/2009 Die Klasse System se 8 © Holger Röder Winter 2008/2009 Programmentwicklung se Datum und Zeit Zur Behandlung von Datumswerten wird die abstrakte Klasse Calendar bzw. die konkrete Klasse GregorianCalendar aus dem Paket java.util verwendet. Die Klasse Date aus dem gleichen Paket sollte in den meisten Fällen wegen fehlender Unterstützung für unterschiedliche Zeitzonen etc. nicht mehr verwendet werden (deprecated). Nützliche „Ausnahme“: Die Methode getTime() liefert die Anzahl Millisekunden seit dem 01.01.1970 00:00:00h GMT zurück. Date t1 = new Date(); // vor Methodenaufruf irgendeineSehrLangeDauerndeMethode(); Date t2 = new Date(); // nach Methodenaufruf long laufzeit = t2.getTime() – t1.getTime(); /* "Laufzeit" in ms - Vorsicht: Garbage Collector, Multithreading ... */ 9 © Holger Röder Winter 2008/2009 Klasse GregorianCalendar Die Klasse GregorianCalendar implementiert den gregorianischen Kalender. Sie bietet Methoden zur Erzeugung von Datumsobjekten und zur Datumsarithmetik. Zeitzonen werden unterstützt. Überladenene Konstruktoren zur Erzeugung eines Datumsobjekts, z. B. GregorianCalendar() // "jetzt" GregorianCalendar(int jahr, int monat, int tag, int stunde, int minute, int sekunde) // Datumsangabe Für Wochentage, Monate etc. sind Konstanten definiert: GregorianCalendar.NOVEMBER, GregorianCalendar.SUNDAY Programmentwicklung GregorianCalendar d = new GregorianCalendar(2006, 4, 12); // 12.05.2006 se String s1 = "Jahr: " + d.get(Calendar.YEAR); String s2 = "Monat:" + d.get(Calendar.MONTH); // Achtung: 0 - 11 String s3 = "Tag: " + d.get(Calendar.DATE); int wtag = d.get(Calendar.DAY_OF_WEEK); // So = 0, Mo = 1 ... /* Abweichung von GMT */ int zeitunterschied = d.get(Calendar.ZONE_OFFSET)/(1000*60*60); 10 © Holger Röder Winter 2008/2009 Programmentwicklung se Datumsvergleiche und -arithmetik Die Methoden equals(), before() und after() erlauben den Vergleich zweier Datumswerte: GregorianCalendar d1 = new GregorianCalendar(2006, 4, 12); // 12. Mai GregorianCalendar d2 = new GregorianCalendar(2006, 4, 14); // 14. Mai if (d1.before(d2)) { ... // d1 liegt vor d2 } Über add() können Zeitspannen addiert oder subtrahiert werden. Die Einheit der Zeitspanne wird über den ersten Parameter festgelegt (YEAR, MONTH, WEEK, DATE etc.): d1.add(Calendar.MONTH, 3); // + 3 Monate -> 12. August 2006 d1.add(Calendar.DATE, -17); // - 17 Tage -> 26. Juli 2006 11 © Holger Röder Winter 2008/2009 Programmentwicklung se Internationalisierung/Lokalisierung Die Standardbibliotheken bieten Unterstützung bei der Internationalisierung von Java-Programmen, z. B. Zeitzonen Datums- und Zahlenformatierung Unterstützung länderspezifischer Zeichensätze bei Ein- und Ausgabe lokalisierte Ressourcen (Beschriftungen, Meldungstexte etc.) Die Klasse java.util.Locale ist Ausgangspunkt für die Internationalisierung. Ein Objekt dieser Klasse identifiziert eine Region der Erde (und damit beispielsweise Sprache und Formatierungsregeln). Über die statische Methode getDefault() kann zur Laufzeit ermittelt werden, in welchem Land das Programm läuft Vorgegebene Instanzen: Locale.GERMANY, Locale.US usw. Locale-Objekte können bei der Formatierung angegeben werden. 12 © Holger Röder Winter 2008/2009 Programmentwicklung se Datumsformatierung Die Formatierung eines Datums in „lesbarer“ und lokalisierter Form erfolgt über die Klasse DateFormat bzw. SimpleDateFormat aus dem Paket java.text. Calendar jetzt = Calendar.getInstance(); String s1 = DateFormat.getTimeInstance().format(jetzt.getTime()); // s1 = 17:35:24 String s2 = DateFormat.getDateInstance().format(jetzt.getTime()); // s2 = 24.11.2006 String s3 = DateFormat.getDateTimeInstance().format(jetzt.getTime()); // s3 = 24.11.2006 17:35:24 String s4 = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, Locale.US).format(jetzt.getTime()); // Internationalisiert (US): November 24, 2006 5:35:24 PM CET SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd ** HH:mm"); String s5 = sd.format(jetzt.getTime()); // s5 = 2006-11-24 ** 17:35 13 © Holger Röder Winter 2008/2009 Programmentwicklung se Formatierung von Zahlen Neben Datumswerten können auch Zahlen (lokalisiert) formatiert werden. Hierfür existieren die Klassen NumberFormat und DecimalFormat aus dem Paket java.text. DecimalFormat df1 = new DecimalFormat("0.00;(0.00)"); DecimalFormat df2 = new DecimalFormat("000E00"); String s1, s2, s3, s4, s5; double x1 = 1234.567; s1 = df1.format(x1); // 1234,57 s2 = df2.format(x1); // 123E01 double x2 = -.9876; s3 = df1.format(x2); // (0,99) s4 = df2.format(x2); // -988E-03 NumberFormat nf_US = NumberFormat.getNumberInstance(Locale.US); s5 = nf_US.format(x1); // US-Format: 1,234.567 Der Formatstring kann aus vorgegebenen Symbolen zusammengesetzt werden. Sym. Bedeutung 0 Einzelne Ziffer # Einzelne Ziffer (keine führende Nullen) . Dezimaltrennzeichen , Tausendertrennzeichen % Prozentdarstellung E Exponentialdarstellung 14 © Holger Röder Winter 2008/2009 Programmentwicklung se Formatierung à la printf Seit Java 5 steht eine der aus C/C++ bekannten printf()-Funktion ähnliche Möglichkeit zur formatierten Ausgabe von Text und Zahlen zur Verfügung: die Klasse Formatter aus dem Paket java.util. Die Funktionalität dieser Klasse steht u. a. bequem über die format()-Methoden in den Klassen String und PrintStream zur Verfügung. for (int i = 1; i < 7; i++) { System.out.format("Rechtsbündig [%04d] ", i*i*i*i); System.out.format("Linksbündig [%-04d]%n", i*i*i*i); } Rechtsbündig Rechtsbündig Rechtsbündig Rechtsbündig Rechtsbündig Rechtsbündig [ 1] [ 16] [ 81] [ 256] [ 625] [1296] Linksbündig Linksbündig Linksbündig Linksbündig Linksbündig Linksbündig [1 ] [16 ] [81 ] [256 ] [625 ] [1296] Linksbündig (-) Breite 4 (4) Ganzzahl (d) Die sehr umfangreichen Formatierungsmöglichkeiten sind in der APIDokumentation beschrieben: http://java.sun.com/javase/6/docs/api/java/util/Formatter.html 15 © Holger Röder Winter 2008/2009 Programmentwicklung se Ein- und Ausgabe Die sequentielle Ein- und Ausgabe wird in Java über sogenannte Datenströme (Streams) realisiert. Dabei wird unterschieden zwischen Byte-Streams zur Ein- und Ausgabe von Bytes (8 Bit) und Character-Streams zur Ein- und Ausgabe von Unicode-Zeichen (16 Bit), insbesondere zur Ein- und Ausgabe von „normalem“ Text. Für die verschiedenen Ein- und Ausgabemöglichkeiten (z. B. UnicodeZeichen in eine Datei schreiben etc.) stehen verschiedene konkrete Implementierungen abstrakter Stream-Klassen zur Verfügung. Weiterhin ist auch wahlfreier Zugriff (random access) zur Ein- und Ausgabe möglich. Die Ein- und Ausgabeklassen gehören zum Paket java.io. 16 © Holger Röder Character-Ausgabe: Writer Winter 2008/2009 (from io) Programmentwicklung CharArrayWriter (f ro m i o) lock : Object PrintWriter se Writer (from io) PrintWriter(arg0 : OutputStream, arg1 : boolean) PrintWriter(arg0 : OutputStream) PrintWriter(arg0 : Writer, arg1 : boolean) PrintWriter(arg0 : Writer) flush() : void close() : void checkError() : boolean setError() : void write(arg0 : int) : void write(arg0 : char[], arg1 : int, arg2 : int) : void write(arg0 : char[]) : void write(arg0 : String, arg1 : int, arg2 : int) : void write(arg0 : String) : void print(arg0 : boolean) : void print(arg0 : char) : void print(arg0 : int) : void print(arg0 : long) : void print(arg0 : float) : void print(arg0 : double) : void print(arg0 : char[]) : void print(arg0 : String) : void print(arg0 : Object) : void println() : void println(arg0 : boolean) : void println(arg0 : char) : void println(arg0 : int) : void println(arg0 : long) : void println(arg0 : float) : void println(arg0 : double) : void println(arg0 : char[]) : void println(arg0 : String) : void println(arg0 : Object) : void Writer(arg0 : Object) Writer() write(arg0 : int) : void write(arg0 : char[]) : void write(arg0 : char[], arg1 : int, arg2 : int) : void write(arg0 : String) : void write(arg0 : String, arg1 : int, arg2 : int) : void flush() : void close() : void FilterWrit er (from io) PipedWriter (from io) St ringWriter (f ro m i o) OutputStreamWriter BufferedWriter (f ro m i o) (f ro m i o) OutputSt reamWriter(arg0 : Output Stream, arg1 : CharsetEncoder) OutputSt reamWriter(arg0 : Output Stream, arg1 : Charset) OutputSt reamWriter(arg0 : Output Stream) OutputSt reamWriter(arg0 : Output Stream, arg1 : String) getEncoding() : String flus hBuffer() : void writ e(arg0 : int) : void writ e(arg0 : char[], arg1 : int, arg2 : int) : void writ e(arg0 : String, arg1 : int, arg2 : int ) : void flus h() : void close() : void BufferedWriter(arg0 : Writer, arg1 : int) BufferedWriter(arg0 : Writer) flushBuffer() : void write(arg0 : int) : void write(arg0 : char[], arg1 : int, arg2 : int) : void write(arg0 : String, arg1 : int, arg2 : int) : void newLine() : void flush() : void close() : void FileWriter (from io) FileWriter(arg0 : FileWriter(arg0 : FileWriter(arg0 : FileWriter(arg0 : FileWriter(arg0 : FileDescriptor) File, arg1 : boolean) File) String, arg1 : boolean) String) 17 © Holger Röder Winter 2008/2009 Programmentwicklung se Beispiel: In Textdatei schreiben Person belegschaft[] = new Person[3]; /* Person(name, alter, gehalt) */ belegschaft[0] = new Person("Alex", 37, 2800.0); belegschaft[1] = new Person("Bastian", 22, 2400.0); belegschaft[2] = new Person("Carlos", 41, 4350.0); try { PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter( "c:/pe/belegschaftDaten.txt"))); for (Person p : belegschaft) { Ausgabe-Stream öffnen, Datei ggf. neu erzeugen out.println(p.getName() + "\t" + p.getAlter() + "\t" + p.getGehalt()); } Ausgabe in Datei (\t = Tabulator) out.close(); } catch (IOException e) { Datei schließen } 18 © Holger Röder Character-Eingabe: Reader Reader (from io) StringReader lock : Object (from io) Reader(arg0 : Object) Reader() read() : int read(arg0 : c har[]) : int read(arg0 : c har[], arg1 : int, arg2 : int) : int skip(arg0 : long) : long ready() : boolean markSupported() : boolean mark(arg0 : int) : void res et() : void close() : void Winter 2008/2009 FilterReader (from io) CharArrayReader (from io) PipedReader (from io) InputStreamReader Programmentwicklung (from io) se InputStreamReader(arg0 : InputStream, arg1 : CharsetDecoder) InputStreamReader(arg0 : InputStream, arg1 : Charset) InputStreamReader(arg0 : InputStream, arg1 : String) InputStreamReader(arg0 : InputStream) getEncoding() : String read() : int read(arg0 : char[], arg1 : int, arg2 : int) : int ready() : boolean close() : void FileReader (f rom io ) FileReader(arg0 : FileDescriptor) FileReader(arg0 : File) FileReader(arg0 : String) BufferedReader (f rom i o) BufferedReader(arg0 : Reader) BufferedReader(arg0 : Reader, arg1 : int) read() : int read(arg0 : char[], arg1 : int, arg2 : int) : int readLine(arg0 : boolean) : String readLine() : String skip(arg0 : long) : long ready() : boolean markSupported() : boolean mark(arg0 : int) : void reset() : void close() : void 19 © Holger Röder Winter 2008/2009 Beispiel: Aus Textdatei lesen try { BufferedReader in = new BufferedReader( new FileReader("c:/pe/belegschaftDaten.txt")); String zeile; while ((zeile = in.readLine()) != null) { String[] tokens = zeile.split("\t"); String name = tokens[0]; int alter = Integer.parseInt(tokens[1]); double gehalt = Double.parseDouble(tokens[2]); Datei für Eingabe öffnen Zeilen einlesen, Tabulator als Trennzeichen Programmentwicklung Person p = new Person(name, alter, gehalt); p.out(); // Hilfsmethode d. Klasse Person se } in.close(); } catch (FileNotFoundException e) { } catch (IOException e) { } Objekte erzeugen, Daten ausgeben Person: Name = Alex, Alter = 37, Gehalt = 2800,0 Person: Name = Bastian, Alter = 22, Gehalt = 2400,0 Person: Name = Carlos, Alter = 41, Gehalt = 4350,0 20 © Holger Röder Byte-Ausgabe: OutputStream (from io) (f rom io ) (from io) Programmentwicklung ByteArray Out putSt ream se #out OutputStream() write(arg0 : int) : void write(arg0 : byte[]) : void write(arg0 : byte[], arg1 : int, arg2 : int) : void flush() : void close() : void PipedOutputStream Winter 2008/2009 FilterOutput Stream OutputStream (f ro m i o) PrintStream (f rom i o) ObjectOutput Stream (f rom i o) FileOutputStream (from io ) FileOutputStream(arg0 : FileDescriptor) FileOutputStream(arg0 : File, arg1 : boolean) FileOutputStream(arg0 : File) FileOutputStream(arg0 : String, arg1 : boolean) FileOutputStream(arg0 : String) write(arg0 : int) : void write(arg0 : byte[]) : void write(arg0 : byte[], arg1 : int, arg2 : int) : void close() : void getFD() : FileDescriptor getChannel() : FileChannel finalize() : void +$out +$err SocketOutputStream System (from net) (f rom l ang) PrintStream(arg0 : OutputStream, arg1 : boolean, arg2 : String) PrintStream(arg0 : OutputStream, arg1 : boolean) PrintStream(arg0 : OutputStream) flush() : void close() : void checkError() : boolean setError() : void write(arg0 : int) : void write(arg0 : byte[], arg1 : int, arg2 : int) : void print(arg0 : boolean) : void print(arg0 : char) : void print(arg0 : int) : void print(arg0 : long) : void print(arg0 : float) : void print(arg0 : double) : void print(arg0 : char[]) : void print(arg0 : String) : void print(arg0 : Object) : void println() : void println(arg0 : boolean) : void println(arg0 : char) : void println(arg0 : int) : void println(arg0 : long) : void println(arg0 : float) : void println(arg0 : double) : void println(arg0 : char[]) : void println(arg0 : String) : void println(arg0 : Object) : void 21 © Holger Röder Byte-Eingabe: InputStream InputStream (from io) System (from lang) Winter 2008/2009 +$in StringBufferInputStream (from io) InputStream() read() : int read(arg0 : byte[]) : int read(arg0 : byte[], arg1 : int, arg2 : int) : int skip(arg0 : long) : long available() : int close() : void mark(arg0 : int) : void reset() : void markSupported() : boolean Byt eArrayInputStream (from io) FileInputStream Programmentwicklung (from io) se ObjectInputSt ream (from io) FileInputSt ream(arg0 : FileDescriptor) FileInputSt ream(arg0 : File) FileInputSt ream(arg0 : String) read() : int read(arg0 : byte[]) : int read(arg0 : byte[], arg1 : int, arg2 : int) : int sk ip(arg0 : long) : long available() : int close() : void getFD() : FileDescript or getChannel() : FileChannel finalize() : void 22 © Holger Röder Winter 2008/2009 Programmentwicklung se Wahlfreier Zugriff (Random Access) Wahlfreier Zugriff ermöglicht das Lesen und Schreiben von/an einer beliebigen Stelle in der Datei. Hierfür steht die Klasse RandomAccessFile zur Verfügung. RandomAccessFile f1 = new RandomAccessFile("C:/pe/binaer.dat", "rw"); for (int i = 0; i < 20; i++) Datei für f1.write((byte) (Math.random() * 255)); Schreiben öffnen f1.close(); RandomAccessFile f2 = new RandomAccessFile("C:/pe/binaer.dat", "r"); System.out.println("Dateigröße: " + f2.length() + " Bytes"); f2.seek(3); Satzzeiger int b = f2.read(); positionieren System.out.println("Byte an Position 3 hat den Wert " + b); f2.close(); Binär-Dump der Datei: 0000000: 0000004: 0000008: 000000c: 0000010: 10100101 01010111 10110011 01001000 11110110 00000010 11111100 11001111 10111010 01001111 00100111 11011011 11110101 11111011 00011001 00001100 11110000 00100110 10000010 10000001 23 © Holger Röder Winter 2008/2009 Programmentwicklung se Serialisierung Wenn Objekte mit ihren Objektbeziehungen gespeichert werden sollen, müssen diese zunächst in ein geeignetes Format konvertiert werden. Dieser Vorgang wird als Serialisierung bezeichnet. Im umgekehrten Fall spricht man von Deserialisierung. Objekte werden serialisiert (d.h. „speichern sich“) (komplexe) Objekte mit gegenseitigen Beziehungen z. B. Datei Objekte werden deserialisiert (d.h. werden instanziert und „laden sich“) 24 © Holger Röder Winter 2008/2009 Programmentwicklung se Serialisierung in Java Java bietet über die Klassen ObjectOutputStream und ObjectInputStream sowie die Schnittstelle Serializable eine einheitliche Möglichkeit zur Serialisierung und Deserialisierung von Objekten. Die vorhandene generische Implementierung funktioniert für primitive Datentypen, viele Klassen des JDK (bspw. Collection-Klassen, Arrays) und für eigene (zusammengesetzte) Klassen. Die „richtige“ Instanziierung als Objekt der entsprechenden Klasse erfolgt automatisch beim Laden. Attribute, die selbst Objekte sind, werden ebenfalls (transitiv) serialisiert. Klassen, die (de-)serialisiert werden sollen, müssen die Schnittstelle Serializable implementieren. 25 © Holger Röder Winter 2008/2009 Programmentwicklung se Versionskonflikte bei der Serialisierung Jede serialisierbare Klasse erhält eine interne „SerialisierungsVersionsnummer“ (serialVersionUID). Anhand dieser Versionsnummer wird geprüft, ob ein serialisiertes Objekt mit der zur Laufzeit verfügbaren (gleichnamigen) Klasse kompatibel ist. Die Versionsnummer sollte in der serialisierbaren Klasse explizit definiert werden, um die Kompatibilität zu gewährleisten: static final long serialVersionUID = 42L; // Long-Zahl Wird für eine Klasse keine Versionsnummer angegeben, wird diese von der Java-Runtime dynamisch aus der Klassenstruktur berechnet. Schon kleine Änderungen der Klasse führen zu unterschiedlichen Versionsnummern und damit zu Inkompatibilitäten: Serialisierte Objekte einer „früheren“ Version der Klasse können nicht mehr deserialisiert werden. 26 © Holger Röder Winter 2008/2009 Programmentwicklung se Objekte speichern Person belegschaft[] = new Person[3]; /* Person(name, alter, gehalt) */ belegschaft[0] = new Person("Alexander", 37, 2800.0); belegschaft[1] = new Person("Bastian", 22, 2400.0); belegschaft[2] = new Person("Carlos", 41, 4350.0); try { ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream("C:/pe/pers.ser")); Stream für Serialisierung out.writeObject(belegschaft); in Datei öffnen out.close(); } catch (FileNotFoundException e) { } catch (IOException e) { Komplexes Objekt } (Person-Array) serialisieren 27 © Holger Röder Winter 2008/2009 Programmentwicklung se Objekte laden try { ObjectInputStream in = new ObjectInputStream( new FileInputStream("C:/pe/pers.ser")); Stream für Deserialisierung Person[] personen = (Person[]) in.readObject(); aus Datei öffnen for (Person p : personen) { p.out(); } Komplexes Objekt lesen; Typecast notwendig, da readObject() } catch (FileNotFoundException e) { formal Object zurückliefert } catch (IOException e) { } catch (ClassNotFoundException e) { } Person: Name = Alex, Alter = 37, Gehalt = 2800.0 Person: Name = Bastian, Alter = 22, Gehalt = 2400.0 Person: Name = Carlos, Alter = 41, Gehalt = 4350.0 28 © Holger Röder Winter 2008/2009 Programmentwicklung se Spezielles Verhalten bei der (De-)Serialisierung Falls bei der Serialisierung oder Deserialisierung zusätzliche klassenspezifische Schritte notwendig sind, können die Methoden readObject und writeObject selbst implementiert werden. Diese Methoden werden beim Serialisieren bzw. Deserialisieren von Objekten dieser Klasse aufgerufen. private void writeObject(ObjectOutputStream s) Standard-Serialisierung aufrufen throws IOException { s.defaultWriteObject(); System.out.println("DEBUG: Objekt serialisiert: " + getName()); } Zusätzliche Schritte bei der Serialisierung private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); System.out.println("DEBUG: Objekt deserialisiert: " + getName()); } 29 © Holger Röder Winter 2008/2009 Programmentwicklung se Dateien und Verzeichnisse Die Hilfsklasse File aus dem Paket java.io dient zur Verwaltung von Dateien und Verzeichnissen. Ein File-Objekt kann eine Datei oder ein Verzeichnis repräsentieren. Wichtige Methoden: exists() – existiert die Datei/das Verzeichnis? isFile() und isDirectory() length() – liefert die Dateigröße (bei Verzeichnissen: 0) getName(), getPath(), getParent() – Namens- und Pfadinformationen delete(), renameTo() – Datei/Verzeichnis löschen, verschieben/umbenennen mkdir() – Verzeichnis anlegen 30 © Holger Röder Winter 2008/2009 Programmentwicklung se File im Überblick, Beispiel File Liefert alle untergeordneten Dateien und Verzeichnisse (from io) File(arg0 : URI) File(arg0 : File, arg1 : String) File(arg0 : String, arg1 : String) File(arg0 : String) getPrefixLength() : int getName() : String getParent() : String getParentFile() : File getPath() : String isAbsolute() : boolean getAbsolutePath() : String getAbsoluteFile() : File getCanonicalPath() : String getCanonicalFile() : File toURL() : URL toURI() : URI canRead() : boolean canWrite() : boolean exists() : boolean isDirectory() : boolean isFile() : boolean isHidden() : boolean lastModified() : long length() : long createNewFile() : boolean delete() : boolean deleteOnExit() : void list() : Logical View::java::lang::String[] list(arg0 : FilenameFilter) : Logical View::java::lang::String[] listFiles() : File[] listFiles(arg0 : FilenameFilter) : File[] listFiles(arg0 : FileFilter) : File[] mkdir() : boolean mkdirs() : boolean renameTo(arg0 : File) : boolean setLastModified(arg0 : long) : boolean setReadOnly() : boolean listRoots() : File[] createTempFile(arg0 : String, arg1 : String, arg2 : File) : File createTempFile(arg0 : String, arg1 : String) : File compareTo(arg0 : File) : int compareTo(arg0 : Object) : int equals(arg0 : Object) : boolean hashCode() : int toString() : String File root = new File("C:/"); for (File child : root.listFiles()) { if (child.isFile()) { System.out.format("%-15s [%d Byte(s)]%n", child.getName(), child.length()); } } AUTOEXEC.BAT BOOT.INI bootfont.bin BOOTLOG.PRV BOOTLOG.TXT ... [0 Byte(s)] [194 Byte(s)] [4952 Byte(s)] [0 Byte(s)] [0 Byte(s)] 31 © Holger Röder Ein Thread ist ein leichtgewichtiger Prozess ohne eigenen Speicherraum. Java-Programme können mehrere parallel laufende Threads besitzen. In Java werden Threads über die Klasse Thread und die Schnittstelle Runnable realisiert. Programmentwicklung Winter 2008/2009 Threads se 32 © Holger Röder Winter 2008/2009 Programmentwicklung se Die Klasse Thread Eigene Threads können durch Ableitung von der Klasse Thread aus dem Paket java.lang erstellt werden. Gestartet wird ein Thread über die Methode start(), die ihrerseits die run()-Methode des Threads aufruft. In der überlagerten Methode run() wird der vom Thread auszuführende Code angegeben. Der Thread läuft, bis das Ende von run() erreicht ist. Jeder Thread hat eine Priorität, die in der Regel seine Ausführungsgeschwindigkeit beeinflusst. Diese kann mit setPriority() verändert werden. Die statische Methode Thread.sleep() pausiert den aktuellen Thread für die angegebene Anzahl Millisekunden. 33 © Holger Röder Winter 2008/2009 Programmentwicklung se Threads – Beispiel public static void main(String[] args) { Unterklasse von Thread class PersonThread extends Thread { private String threadName; private Person person; public PersonThread(String threadName, Person person) { this.threadName = threadName; this.person = person; run-Methode implementiert die } Funktionalität des Threads @Override public void run() { for (int i = 0; i < 50; i++) { person.erhoeheAlter(); System.out.println("(" + threadName + ") " + person.getAlter()); } } Instanziieren der Threads } Person p = new Person("Mona Maurer", 23, 2500.0); PersonThread t1 = new PersonThread("Thread 1", p); PersonThread t2 = new PersonThread("Thread 2", p); t1.start(); t2.start(); Starten der Threads } 34 © Holger Röder Winter 2008/2009 Programmentwicklung se Threads und Synchronisation Unsynchronisiert Kurzzeitig Unsynchronisiert Langdauernd Synchronisiert Langdauernd public void erhoeheAlter() { int a = getAlter(); setAlter(a + 1); } (Thread (Thread (Thread (Thread (Thread (Thread 1) 2) 1) 2) 1) 2) 24 25 26 27 28 29 public void erhoeheAlter() { int a = getAlter(); try { Thread.sleep(200); } catch (InterruptedException e) { } setAlter(a + 1); } (Thread (Thread (Thread (Thread (Thread (Thread 1) 2) 2) 1) 2) 1) 24 24 25 25 26 26 synchronized public void erhoeheAlter() { int a = getAlter(); try { Thread.sleep(200); } catch (InterruptedException e) { } setAlter(a + 1); } (Thread (Thread (Thread (Thread (Thread (Thread 1) 2) 1) 2) 1) 2) 24 25 26 27 28 29 35 © Holger Röder Winter 2008/2009 Programmentwicklung se Synchronisation In Java kommunizieren Threads über gemeinsame Daten. Führen mehrere Threads Änderungen an diesen Daten durch, kann es zu Synchronisationsproblemen kommen. Java nutzt das Monitor-Konzept zur Synchronisation nebenläufiger Prozesse: kritische Bereiche werden mit einer automatischen Sperre versehen, die dafür sorgt, dass jeweils nur ein Prozess gleichzeitig diesen Programmteil durchläuft. Zur Definition solcher kritischen Bereiche dient das Schlüsselwort synchronized. Es können komplette Methoden und einzelne Blöcke geschützt werden: Bei Methoden wird der this-Zeiger als Sperre verwendet. Bei synchronized-Blöcken muss das als Sperre zu verwendende Objekt (beliebigen Typs) explizit angegeben werden: synchronized (sperrObjekt) { ... } 36 © Holger Röder Winter 2008/2009 Programmentwicklung se Die Schnittstelle Runnable Soll eine Klasse als Thread laufen, aber nicht von Thread abgeleitet werden, kann sie die Schnittstelle Runnable implementieren. Ein Thread-Hilfsobjekt, dem ein Objekt dieser Klasse im Konstruktor übergeben wurde, sorgt dann für die Ausführung als Thread. class Sterne implements Runnable { public void run() { for (int i = 0; i < 100; i++) { System.out.print("*"); } } } Sterne sterne = new Sterne(); Thread t = new Thread(sterne); t.start(); Klasse implementiert die Schnittstelle Runnable Spezieller Thread-Konstruktor 37 © Holger Röder Winter 2008/2009 Programmentwicklung se Datenstrukturen Das JDK stellt im Paket java.util Implementierungen verschiedener komplexer Datenstrukturen bereit. Zentrale Schnittstellen sind Collection und Map. Assoziative Speicher mit Schlüssel-Wert-Zuordnungen (z. B. HashTabellen) implementieren die Schnittstelle Map. Alle anderen Datenstrukturen implementieren die Schnittstelle Collection. Diese definiert die gemeinsamen Zugriffsmethoden (Auswählen, Einfügen, Löschen, Suchen etc.). Collection-Datenstrukturen können weiter unterteilt werden in Listen (Schnittstelle List) und Mengen (Schnittstelle Set). 38 © Holger Röder Programmentwicklung Winter 2008/2009 Übersicht: Collection, List, Set se Ein Objekt darf höchstens einmal enthalten sein (Mengensemantik) Ein Objekt kann mehrfach enthalten sein 39 © Holger Röder Programmentwicklung Winter 2008/2009 Übersicht: Collection und Implementierungen se Ein Objekt darf höchstens einmal enthalten sein (Mengensemantik) Ein Objekt kann mehrfach enthalten sein 40 © Holger Röder Winter 2008/2009 Programmentwicklung se Schnittstelle List und Implementierungen Collections vom Typ List repräsentieren geordnete Listen von Elementen: Elemente können mehrfach in der Liste enthalten sein. Auf die Elemente kann entweder sequentiell oder wahlfrei über einen Index zugegriffen werden. Implementierungen der Schnittstelle List im Paket java.util: LinkedList: interne Realisierung als doppelt verkettete Liste ArrayList: interne Realisierung als Array Vector: interne Realisierung ebenfalls als Array, synchronisierte Zugriffsmethoden 41 © Holger Röder Winter 2008/2009 Programmentwicklung se Die Klasse Vector Vector ist eine konkrete Implementierung der List-Schnittstelle und repräsentiert dynamische Arrays. Im Unterschied zu normalen Arrays können Vector-Objekte „wachsen“, d.h. die Anzahl enthaltener Objekte ist auch zur Laufzeit variabel. Einfügen, Entfernen und Auslesen von Elementen sind leicht möglich. Vector ist eine generische Klasse und sollte typisiert werden. Person p1 = new Person("Alexander", 37, 2800.0); Person p2 = new Person("Bastian", 22, 2400.0); Person p3 = new Person("Carlos", 41, 4350.0); Elemente einfügen, auslesen, entfernen List<Person> v = new Vector<Person>(); Objekt erzeugen v.add(p1); v.add(p2); Person p = v.get(0); // liefert "Alexander" v.remove(0); v.add(0, p3); p = v.get(0); // liefert "Carlos" 42 © Holger Röder Winter 2008/2009 Programmentwicklung se Iterieren über eine Datenstruktur: Iterator Iteratoren sind Objekte, die zum Durchlaufen von Datenstrukturen dienen. In Java existiert hierzu die Schnittstelle Iterator. Iterator definiert drei Methoden: hasNext() liefert true zurück, wenn die Datenstruktur, zu der der Iterator gehört, ein weiteres Element enthält. next() liefert dieses nächste Element. remove() entfernt das zuletzt geholte Element. Wichtig: alle anderen Änderungen (Einfügen, Sortieren etc.) an der Datenstruktur führen zu einem undefinierten Zustand des Iterators! 43 © Holger Röder Winter 2008/2009 Programmentwicklung se Collection, Iterator und Iterable Die Schnittstelle Iterable kennzeichnet eine Datenstruktur als iterierbar: über die definierte Methode iterator() kann ein passendes Iterator-Objekt geholt werden. Die Schnittstelle Collection ist von der Schnittstelle Iterable abgeleitet. Über Collection-Datenstrukturen können somit iteriert werden. Über die Methode iterator() kann für jedes Objekt, das Collection implementiert, ein Iterator-Objekt geholt werden. Das Iterieren über die Elemente kann mit einer erweiterten forSchleife erfolgen. 44 © Holger Röder Winter 2008/2009 Programmentwicklung se Beispiel: Vector und Iterator Vector implementiert Collection, also kann die Datenstruktur mit einem Iterator durchlaufen werden. Person p1 = new Person("Alexander", 37, 2800.0); Person p2 = new Person("Bastian", 22, 2400.0); Person p3 = new Person("Carlos", 41, 4350.0); Vector<Person> v = new Vector<Person>(); v.add(p1); v.add(p2); v.add(p3); Iterator-Objekt holen Iterator<Person> iter = v.iterator(); while (iter.hasNext()) { Person p = iter.next(); p.out(); } /* Alternativ: */ for (Person p: v) { p.out(); } true, wenn noch ein Element enthalten ist nächstes Element “elegantere” Lösung mit erweiterter for-Schleife 45 © Holger Röder Winter 2008/2009 Programmentwicklung se Schnittstelle Set und Implementierungen Collections vom Typ Set repräsentieren Mengen: Kein Element darf mehrfach enthalten sein. Die Prüfung auf Gleichheit erfolgt beim Einfügen über equals(). Die Elemente haben keine definierte Reihenfolge. Implementierungen der Schnittstelle Set im Paket java.util: HashSet: interne Speicherung als Hash-Tabelle, generische Klasse Set<String> menge = new HashSet<String>(); String s1 = "Hallo Welt"; String s2 = "Hallo" + " Welt"; boolean erfolg; erfolg = menge.add(s1); // true erfolg = menge.add(s2); // false, schon enthalten! Für sortierte Mengen steht die Schnittstelle SortedSet und die Implementierung TreeSet zur Verfügung. 46 © Holger Röder Winter 2008/2009 Programmentwicklung se Schnittstelle Map und Implementierungen Datenstrukturen vom Typ Map repräsentieren assoziative Speicher, in denen Schlüssel auf Werte (also Elemente) abgebildet werden. Für jeden Schlüssel gibt es entweder kein oder genau ein zugeordnetes Wert-Element. Der Zugriff auf die gespeicherten Elemente erfolgt über den Schlüssel. Zugriffsmethoden (vereinfacht): put(key, value) und get(key) Implementierungen der Schnittstelle Map im Paket java.util: HashMap: interne Realisierung als Hash-Tabelle Hashtable: interne Realisierung ebenfalls als Hash-Tabelle, synchronisierte Zugriffsmethoden 47 se Programmentwicklung Winter 2008/2009 © Holger Röder Übersicht: Map 48 © Holger Röder Winter 2008/2009 Programmentwicklung se Die Klasse HashMap Die Klasse HashMap implementiert die Schnittstelle Map und repräsentiert eine Hash-Tabelle. HashMap sollte zweifach typisiert werden (Schlüssel und Werte). Person p1 = new Person("Alexander", 37, 2800.0); Person p2 = new Person("Bastian", 22, 2400.0); Person p3 = new Person("Carlos", 41, 4350.0); Map<String, Person> map = new HashMap<String, Person>(); map.put(p1.getName(), p1); HashMap anlegen: map.put(p2.getName(), p2); Schlüssel: String, Wert: Person map.put(p3.getName(), p3); Person p = map.get("Bastian"); p.out(); for (String key: map.keySet()) { System.out.println(key); } for (Person value: map.values()) { value.out(); } Einfügen und Auslesen Iterieren über alle Schlüssel bzw. alle Werte 49 © Holger Röder Um die Elemente einer Datenstruktur zu sortieren, muss eine Ordnung auf den Elementen definiert werden. Hierzu existieren zwei Möglichkeiten: Die Elemente implementieren die Schnittstelle Comparable (Paket java.lang). Ein explizites Vergleichsobjekt, das die Schnittstelle Comparator implementiert, übernimmt die Sortierung. Programmentwicklung Winter 2008/2009 Sortierung se 50 © Holger Röder Winter 2008/2009 Programmentwicklung se Schnittstelle Comparable Die Schnittstelle Comparable definiert eine Methode: public int compareTo(Object o) Rückgabewert Bedeutung < 0 Aktuelles Objekt liegt vor dem zu vergleichenden Objekt. = 0 Aktuelles Objekt und zu vergleichendes Objekt sind gleich. > 0 Aktuelles Objekt liegt hinter dem zu vergleichenden Objekt. Wenn möglich sollte die Implementierung typisiert werden: die compareTo()-Methode erhält dann keine Object-Parameter, sondern entsprechend typisierte Parameter. Beispiel: Sortierung von Person-Objekten anhand des Namens: public class Person implements Comparable<Person> { ... Aufruf der compareTo()-Methode public int compareTo(Person p2) { der Klasse String Person p1 = this; return p1.getName().compareTo(p2.getName()); } } 51 © Holger Röder Schnittstelle Comparator Analog zu Comparable definiert Comparator nur eine Methode: public int compare(Object o1, Object o2) Programmentwicklung Winter 2008/2009 Auch hier ist eine Typisierung möglich. se Beispiel: PersonComparator sortiert absteigend nach Namen public class PersonComparator implements Comparator<Person> { public int compare(Person o1, Person o2) { return o2.getName().compareTo(o1.getName()); // Alternativ: Sortierung aufsteigend nach Alter // return p1.getAlter() – p2.getAlter(); } } 52 © Holger Röder Winter 2008/2009 Sortierung der Datenstrukturen Sortierte Datenstrukturen im Paket java.util: Sortierte Mengen: Schnittstelle SortedSet und Implementierung TreeSet Sortierte assoziative Speicher (Sortierung der Schlüssel): Schnittstelle SortedMap und Implementierung TreeMap Beispiel: Person p1 = new Person("Alexander", 37, 2800.0); Person p2 = new Person("Bastian", 22, 2400.0); Person p3 = new Person("Carlos", 41, 4350.0); Programmentwicklung SortedSet<Person> menge = new TreeSet<Person>(new PersonComparator()); se menge.add(p2); menge.add(p3); menge.add(p1); for (Person p: menge) p.out(); Person: } Person: Person: Übergabe des ComparatorObjekts an den Konstruktor { Name = Carlos, Alter = 41, Gehalt = 4350.0 Name = Bastian, Alter = 22, Gehalt = 2400.0 Name = Alexander, Alter = 37, Gehalt = 2800.0 53