5.4 Objektorientierte Bausteine und Bibliotheken

Werbung
5.4 Objektorientierte Bausteine
und Bibliotheken
Dieser Abschnitt führt in den Komponenten-/
Bausteinbegriff und in die Java-Bibliothek ein.
Ziel ist die Vertiefung der in 5.3 erläuterten
Konzepte und deren Anwendung.
Überblick:
• Bausteine, Schnittstellen und Bibliotheken
• Klassen zur Ausnahmebehandlung
• Ströme zur Ein- und Ausgabe
5.4.1 Bausteine, Schnittstellen und
Bibliotheken
• Klärung und Diskussion der Begriffe
- Programmbaustein, -komponente
- API, Programmierschnittstelle
- Bibliothek
• Überblick über die Java-Bibliothek
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
211
Programmbausteine & Komponenten
Wir verwenden die Begriffe „Programmbaustein“
und „Programmkomponente“ hier synonym.
Idee:
Konstruiere Softwaresysteme aus vorgefertigten
Bausteinen durch
• Anpassen der Bausteine:
- Instanzieren von Parametern
- Spezialisierung
• Verbinden der Bausteine/Komposition:
- Verbindungsmechanismen der Komponenten
- zusätzlicher Programmcode (engl. glue code)
Wichtige Fragestellungen:
- Wie anpassbar, wie allgemein sind die Bausteine?
- Sind die Bausteine direkt einsetzbar oder müssen
sie noch vervollständigt werden?
- Sind die Bausteine unabhängig voneinander,
hierarchisch strukturiert, wechselseitig abhängig?
- Wie werden sie zusammengesetzt?
- Wie kann man sie auffinden?
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
212
Vereinfachend gehen wir davon aus, dass ein
Baustein durch eine oder mehrere Typdeklarationen
realisiert ist. Die Schnittstelle eines Bausteins
entspricht der öffentlichen Schnittstelle dieser Typen.
Wir betrachten drei Arten von Bausteinen:
- unabhängige Bausteine
- eigenständige Bausteine
- kooperierende Bausteine
Wir erläutern diese Aufteilung jeweils durch
Begriffsklärung und Beispiele in Java.
Begriffsklärung: (unabhängige Bausteine)
Wir nennen Bausteine oder Programmkomponenten
unabhängig, wenn sie ohne Kenntnis und Existenz
anderer Bausteine bzw. Komponenten angewandt
werden können.
In der Schnittstelle unabhängiger Bausteine kommen
im Wesentlichen nur vordefinierte und Standardtypen
vor.
Typische Beispiele sind einfache Behälterbausteine
(z.B. SLinkedList), die Klasse String oder die
Wrapper-Klassen, aber auch vollständige
Anwendungsprogramme.
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
213
Beispiel: (unabhängiger „Baustein“)
public final
class Boolean implements java.io.Serializable {
public static final Boolean TRUE =
new Boolean(true);
public static final Boolean FALSE =
new Boolean(false);
public static final Class TYPE = ...;
public Boolean(boolean value) {
this.value = value;
}
public Boolean(String s) {
this( toBoolean(s) );
}
public boolean booleanValue(){ return value; }
public static Boolean valueOf(String s){...}
public String toString() { ... }
public int hashCode() { ... }
public boolean equals(Object obj) {
if (obj instanceof Boolean) {
return value ==
((Boolean)obj).booleanValue();
}
return false;
}
public static boolean getBoolean(String n){..}
}
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
214
Unabhängigkeit erleichtert das Verständnis
von Bausteinen. Trotzdem sind unabhängige
Bausteine in Programmbibliotheken eher selten.
Häufig sind unterschiedliche, aber ähnliche
Bausteine Teil einer Klassen- bzw. Typhierarchie.
Dadurch sind speziellere Bausteine oft von
allgemeineren abhängig.
Begriffsklärung: (eigenständige Bausteine)
Wir nennen Bausteine oder Programmkomponenten
eigenständig, wenn die Kenntnis und Existenz ihrer
Supertypen für ihr Verständnis und ihre Anwendung
ausreicht und sie eingesetzt werden können, ohne
mit anderen Bausteinen kooperieren zu müssen.
In der Schnittstelle eigenständiger Bausteine kommen
im Wesentlichen nur vordefinierte Typen, Standardtypen und Supertypen der Klassen vor, die den Baustein implementieren.
Typische Beispiele sind komplexere Behälterbausteine
und Stromklassen.
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
215
Beispiel: (eigenstängiger Baustein)
Listen mit Iteratoren bilden einen Baustein. Als
Beispiel betrachten wir die Typen LinkedList, Iterator
und ListIterator aus dem Paket java.util.
public interface Iterator {
boolean hasNext();
Object next();
void remove();
}
public interface ListIterator
extends Iterator {
boolean hasNext();
Object next();
boolean hasPrevious();
Object previous();
int nextIndex();
int previousIndex();
void remove();
void set(Object o);
void add(Object o);
}
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
216
public class LinkedList
extends AbstractSequentialList
implements List, Cloneable, Serializable
{
public LinkedList()
public LinkedList(Collection c)
public Object getFirst()
public Object getLast()
public Object removeFirst()
public Object removeLast()
public void addFirst(Object o)
public void addLast(Object o)
public boolean contains(Object o)
public int size()
public boolean add(Object o)
public boolean remove(Object o)
public boolean addAll(Collection c)
public boolean addAll(int ix, Collection c)
public void clear()
public Object get(int index)
public Object set(int ix, Object elem)
public void add(int index, Object elem)
public Object remove(int index)
public int indexOf(Object o)
public int lastIndexOf(Object o)
// ... weiter nächste Seite
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
217
// ...
public
public
public
public
public
public
public
public
public
... //
ListIterator listIterator()
ListIterator listIterator(int ix)
Iterator iterator()
List subList(int from, int to)
boolean equals(Object o)
int hashCode()
Object clone()
Object[] toArray()
Object[] toArray(Object a[])
Methoden aus Object
}
Bemerkung:
In Abschnitt 5.4.3 werden wir weitere eigenständige
Bausteine kennen lernen.
Im Allg. agieren Bausteine nicht eigenständig,
sondern müssen mit anderen und andersartigen
Bausteinen kooperieren, um ihre Funktionalität
zu erbringen.
Bausteine können eng oder nur lose kooperieren.
Enge Kooperation findet man typischerweise in
Programmgerüsten (engl. program frameworks).
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
218
Die Kooperation zwischen Bausteinen kann
man syntaktisch an den (rekursiven) Abhängigkeiten
der Typdeklarationen erkennen.
Beispiel: (Abhängigkeiten bei Kooperation)
Folgende Fragmente stammen aus dem AWT
(Pakete java.awt, etc.), einem Java-Framework
zur Implementierung graphischer Bedienoberflächen.
class Component ... {
...
void addComponentListener(ComponentListener c)
...
}
interface ComponentListener ... {
...
void componentResized( ComponentEvent e );
...
}
class ComponentEvent ... {
...
Component getComponent() ...
...
}
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
219
Bemerkung:
• Die Grenzen eines Bausteins werden vom
Bausteinentwickler festgelegt und lassen sich
im Allg. nicht aus dem Programmtext erschließen.
• Die Unterscheidung zwischen den Bausteinarten
ist nicht scharf.
Begriffsklärung: (Programmierschnittstelle)
Die Programmierschnittstelle eines Bausteins (einer
Komponente) besteht aus den öffentlichen Typen
und Methoden, mit denen der Baustein aus
Programmen heraus gesteuert werden kann.
In gleicher Weise spricht man von der Programmierschnittstelle einer Anwendung oder eines Systems
(engl. application programming interface, API).
Beispiele: (Programmierschnittstellen)
Es gibt Programmierschnittstellen für den:
• Zugriff aufs Dateisystem
• Zugriff auf andere Teile des Betriebssystems
• Zugriff aufs Netzwerk
• Zugriff auf eine Anwendung, etwa ein Spiel
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
220
Bemerkung:
Nicht alles, was sich API nennt, ist in obigem Sinne
eine Programmierschnittstelle.
Begriffsklärung: (Programmbibliothek)
Eine Programmbibliothek oder einfach Bibliothek
ist eine strukturierte Ansammlung von Programmteilen,
die für die Wiederverwendung entwickelt und
organisiert sind.
Die Programmteile können unterschiedlicher Art
sein (z.B. standardisierte Bausteine der Sprache,
eigenständige Bausteine, Programmierschnittstellen,
Programmgerüste).
Beispiele: (Programmbibliotheken)
• zu Programmiersprachen gehörende Bibliotheken
(z.B. Java-Bibliothek)
• Bibliotheken für Datenstrukturen: STL, Leda, ...
• Bibliotheken für graphische Bedienoberflächen,
zu allgemeinen Anwendungsbaukästen
• Bibliotheken für Graphik, CAD, virtuelle Welten, ...
• Anwendungsspezifische Bibliotheken
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
221
Wichtige Pakete der Java-Bibliothek
applet
event
awt
font
beans
image
io
ref
lang
java
math
reflection
net
nio
registry
rmi
server
security
sql
text
zip
javax.*
util
jar
org. *
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
222
Bemerkung:
Die obige Darstellung der Java-Bibliothek ist
unvollständig. Beispielsweise enthält das
Paket javax.swing ein leistungsfähigeres
Programmgerüst zur Realisierung graphischer
Bedienoberflächen (GUI Framework).
5.4.2 Klassen zur Ausnahmebehandlung
Realisierung der Ausnahmebehandlung in Java
demonstriert zwei Aspekte:
- erweiterbare Hierarchie einfacher Bausteine
- Zusammenspiel von Sprache und Standardbibliothek
Darüber hinaus ist das Verständnis der Ausnahmebehandlung von allgemeinem Interesse für die
Softwareentwicklung.
Klassifikation von Ausnahmesituationen
Die möglichen Ausnahmesituationen werden in
Java durch eine Typhierarchie klassifiziert.
Alle Ausnahmeklassen sind Subklassen von
java.lang.Throwable
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
223
Es gibt drei Arten von Ausnahmesituationen:
1. Nicht korrigierbare Ausnahmesituationen (Error):
Drei typische Beispiele:
- kein Speicherplatz mehr verfügbar zum Erzeugen
von Objekten (OutOfMemoryError)
- zuviele unbeendete Methodenaufrufe
(StackOverflowError)
- Inkonsistenzen zwischen übersetzten Klassen
(z.B. AbstractMethodError)
2. Programmierfehler (RuntimeException):
Typische Beispiele:
- Dereferenzieren von null (NullPointerException):
Object ov = null;
ov.equals( new Object() );
- Feldzugriff mit unzulässigem Index
(IndexOutOfBoundsException):
int[] iav = new int[37];
int n = iav[333];
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
224
3. Nicht vermeidbare, aber behandelbare Situationen:
Beispiele sind der Zugriff auf eine Ressource,
etwa das Netz oder eine Datei, die augenblicklich
anderweitig genutzt wird.
Abhilfe:
- zeitverzögert nochmaligen Zugriff
- Anwendung einer vergleichbaren Ressource.
In diese Kategorie fallen auch die meisten anwendungsspezifischen Ausnahmen.
Beispiel: (anwendungsspez. Ausnahmekl.)
public class KeinKaffeeException
extends Exception {
private float restMenge;
KeinKaffeeException( float kaffeeMenge ){
restMenge = kaffeeMenge;
}
public float getRestMenge() {
return restMenge;
}
}
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
225
Insgesamt ergibt sich folgende Klassifikation bzw.
Typhierarchie:
Object
Throwable
Error
Exception
IOException
AbstractMethodError
ClassNotFoundException
OutOfMemoryError
KeinKaffeeException
StackOverflowError
RuntimeException
NullPointerException
IndexOutOfBoundsException
IllegalArgumentException
NumberFormatException
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
226
Zusammenspiel von Sprache und Bibliothek
Die Ausnahmebehandlung in Java hat vier Aspekte:
- Welche Ausnahmen gibt es?
- Wodurch werden Ausnahmen ausgelöst?
- Wie werden Ausnahmen ggf. über Methodengrenzen hinweg weitergereicht?
- Wie kann man Ausnahmen abfangen und behandeln?
1. Aspekt:
In Java werden Ausnahmen als Subklassen von
Throwable realisiert. D.h.
- vordefinierte Ausnahmen in der Standardbibliothek
- benutzerdefinierte Ausnahmen als Subklassen
2. Aspekt:
Fehler und Laufzeitausnahmen werden durch das
Laufzeitsystem von Java (Java Virtual Machine)
ausgelöst.
Andere Ausnahmen durch Systemschnittstellen oder
Anwenderprogramme mittels der throw-Anweisung.
Der Ausdruck in der throw-Anweisung muss ein
Subtyp von Throwable sein.
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
227
3. Aspekt:
Behandlung von Ausnahmen:
- im umfassenden try-Block;
- die aktuelle Methode terminiert abrupt und reicht die
Ausnahme an die Aufrufstelle weiter.
Der Java-Übersetzer prüft, welche Ausnahmen eine
Methode möglicherweise auslöst, ohne sie abzufangen.
Alle nicht-abgefangenen Ausnahmen, die weder
Subklassen von Error noch von RunTimeException
sind, müssen in der Signatur der Methode deklariert
werden (siehe Beispiel).
4. Aspekt:
Mittels der try-catch-Anweisung kann man Ausnahmen
(und Fehler) abfangen:
try {
<Anweisungsblock1>
} catch( <Subtyp von Throwable> eb ) {
<Anweisungsblock2>
} ...
Tritt im Anweisungsblock1 eine Ausnahme vom Typ E
auf und ist E ein Subtyp des Typs, der in der catchKlausel genannt ist, dann wird die Ausnahme
abgefangen und der Anweisungsblock2 ausgeführt.
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
228
Andernfalls wird die Ausnahme weitergereicht.
Beispiel: ( Ausnahmebehandlung)
public class KaffeeMaschine {
private KaffeeSpeicher speicher;
...
void fuellenFilter( float benoetigteMenge )
throws KeinKaffeeException {
float restMenge;
restMenge = speicher.messenFuellung();
if( restMenge < benoetigteMenge ) {
throw new KeinKaffeeException(restMenge);
}
...
}
}
Bemerkung:
• Häufig kann die Behandlung der Ausnahme erst
von einem weiter außen liegenden Aufrufer erledigt
werden.
• Eine überschreibende Methode darf nur die Ausnahmen auslösen, die auch die überschriebene
Methode auslösen darf.
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
229
5.4.3 Ströme zur Ein- und Ausgabe
Ein- und Ausgabe von Daten wird heutzutage meist
durch Ströme modelliert.
Begriffsklärung: (Datenstrom)
Ein Strom ist eine potentiell unendliche Folge von
Daten. Er wird von einer oder mehrerer Quellen mit
Daten versorgt und erlaubt es, diese Daten der
Reihe nach aus dem Strom herauszulesen.
Das Ende eines Stromes wird durch ein spezielles
Datum (in Java ist das -1) markiert.
Sowohl beim Schreiben in einen Strom als auch beim
Lesen aus einen Strom kann es zu Verzögerungen
kommen:
- beim Lesen, weil augenblicklich kein Zeichen
vorhanden, der Strom aber noch nicht zu Ende ist;
- beim Schreiben, weil ggf. kein Platz im Strom
vorhanden ist.
Die Verzögerungen führen zu einer Blockierung der
ausgeführten Methode.
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
230
Bemerkung:
• Stromklassen sind in diesem Kontext interessant:
- als wichtige Programmierschnittstelle
- als Beispiel für Typhierarchien und eigenständige
Bausteine
- als Beispiel für Komposition von Bausteinen
• Vergleiche auch unendliche Listen in ML (F. 3.82).
Einführung in Ströme
Wir betrachten zunächst Ströme zum Lesen:
interface CharEingabeStrom {
int read() throws IOException;
}
Diese Schnittstelle abstrahiert von der Quelle
aus der gelesen wird. Mögliche Quellen:
1. Datenstruktur wie Feld, Liste, String.
2. Datei
3. Netzwerk
4. Standardeingabe, d.h. interaktive vom Anwender
5. andere Programme
6. andere Ströme
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
231
Wir betrachten hier die Fälle 1, 2 und 6.
Lesen aus einer Datenstruktur:
Wir betrachten das schrittweise Lesen der Zeichen
eines Strings:
public class StringLeser
implements CharEingabeStrom {
private char[] dieZeichen;
private int
index = 0;
public StringLeser( String s ) {
dieZeichen = s.toCharArray();
}
public int read() {
if( index == dieZeichen.length )
return -1;
else return dieZeichen[index++];
}
}
Die Quelle des Stroms wird dem Stromkonstruktor
übergeben.
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
232
Zusammenbauen von Strömen:
Wir betrachten zunächst zwei Stromklassen, die aus
anderen Strömen lesen und diese ändern.
public class GrossBuchstabenFilter
implements CharEingabeStrom {
private CharEingabeStrom eingabeStrom;
public GrossBuchstabenFilter(
CharEingabeStrom cs ) {
eingabeStrom = cs;
}
public int read() throws IOException {
int z = eingabeStrom.read();
if( z == -1 ) {
return -1;
} else {
return Character.toUpperCase( (char)z );
}
}
}
Der Konstruktor nimmt einen beliebigen
CharEingabeStrom als Quelle:
 Subtyping at its best!
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
233
public class UmlautSzFilter
implements CharEingabeStrom {
private CharEingabeStrom eingabeStrom;
private int puffer = -1;
public UmlautSzFilter( CharEingabeStrom cs ){
eingabeStrom = cs;
}
public int read() throws IOException {
if( puffer != -1 ) {
int z = puffer;
puffer = -1;
return z;
} else {
int z = eingabeStrom.read();
if( z == -1 ) return -1;
switch( (char)z ) {
case '\u00C4': puffer = 'e'; return
case '\u00D6‚: puffer = 'e'; return
case '\u00DC': puffer = 'e'; return
case '\u00E4': puffer = 'e'; return
case '\u00F6': puffer = 'e'; return
case '\u00FC': puffer = 'e'; return
case '\u00DF': puffer = 's'; return
default:
return z;
}
}
}
'A';
'O';
'U';
'a';
'o';
'u';
's';
}
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
234
Folgendes Programm zeigt Zusammenbau
und Anwendung von Strömen:
public class Main {
public static void main(String[] args)
throws IOException {
String s = new String(
"\u00C4neas opfert den G\u00D6ttern "
+ "edle \u00D6le,\nauf "
+ "da\u00DF \u00FCberall "
+ "das \u00DCbel sich \u00E4ndert.");
CharEingabeStrom cs;
cs = new StringLeser( s );
cs = new UmlautSzFilter( cs );
cs = new GrossBuchstabenFilter( cs );
int z = cs.read();
while( z != -1 ) {
System.out.print( (char)z );
z = cs.read();
}
System.out.println("");
}
}
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
235
Adaption von Strömen:
Adaption bedeutet in der Objektorientierung
meist das Anpassen einer Schnittstelle an die
Bedürfnisse eines Anwenders.
Als kleines Beispiel einer Adaption betrachten
wir die typmäßige Anpassung der Klasse
FileReader aus java.io an CharEingabeStrom:
CharEingabeStrom
FileReader
DateiLeser
Da FileReader eine Methode read mit der
gleichen Signatur und Bedeutung wie in
CharEingabeStrom bereitstellt, reicht folgende
fast triviale Adaptionsklasse:
public class DateiLeser
extends FileReader
implements CharEingabeStrom
{
public DateiLeser( String s )
throws IOException {
super(s);
}
}
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
236
Javas Stromklassen
Stromklassen werden nach den Datentypen,
die sie verarbeiten, und ihre Quellen bzw. Senken
klassifiziert.
Stromklassen sind wichtige programmiertechnische
Hilfsmittel und ihre Hierarchien ein gutes Beispiel
für eigenständige Bausteine.
Die Reader-/Writer-Klassen aus dem Paket java.io
verarbeiten char-Ströme; die Input-/Output-Stromklassen verarbeiten byte-Ströme.
Die Reader-Klassen unterstützen:
• das Lesen einzelner Zeichen: int read() ;
• das Lesen mehrerer Zeichen aus der Quelle und
Ablage in ein char-Feld: int read(char[]) u. ä.;
• das Überspringen einer Anzahl von Zeichen der
Eingabe: long skip(long) ;
• die Abfrage, ob der Strom für das Lesen des
nächsten Zeichens bereit ist ;
• das Schließen des Eingabestroms: void close();
• Methoden zum Markieren und Zurücksetzen des
Stroms.
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
237
Die Writer-Klassen unterstützen:
• das Schreiben einzelner Zeichen:
void write( int ) ;
• das Schreiben mehrerer Zeichen eines charFeldes: void write(char[]) u. ä.;
• das Schreiben mehrerer Zeichen eines String:
void write(String) u. ä.;
• die Ausgabe ggf. im Strom gepufferter Zeichen:
void flush() ;
• das Schließen des Ausgabestroms: void close().
Die genannten Methoden lösen möglicherweise
eine IOException aus.
Die von InputStream bzw. OutputStream abgeleiteten Klassen leisten Entsprechendes für Daten
vom Typ byte.
Reader-/Writer-Klassen:
Die Reader-Klassen unterscheiden sich im
Wesentlichen durch ihre Quelle:
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
238
Reader
InputStreamR
PipedR
BufferedR
FileR
CharArrayR
StringR
FilterR
LineNumberR
PushBackR
Reader-Klasse
Quelle
InputStreamReader
InputStream
FileReader
byte-Strom aus Datei
BufferedReader
Reader
puffernd; kann
LineNumberReader
Reader
zeilenweise lesen
PipedReader
PipedWriter
FilterReader
Reader
PushBackReader
Reader
CharArrayReader
char[]
StringReader
String
08.01.09
Bemerkung
Methode unread
© A. Poetzsch-Heffter, TU Kaiserslautern
239
Writer
OutputStreamW
PipedW
BufferedW
CharArrayW
FilterW
StringW
PrintW
FileW
Writer arbeiten entsprechend den Reader-Klassen,
nur in umgekehrter Richtung.
PrintWriter unterstützen die formatierte Ausgabe
von Daten durch die Methoden print und println, die
alle Standarddatentypen als Parameter nehmen.
Bemerkung:
Die Konstruktoren ermöglichen das Zusammenhängen
von Strömen; hier am Beispiel eines Konstruktors der
Klasse PrintWriter:
public PrintWriter(OutputStream o,boolean af){
this(new BufferedWriter(
new OutputStreamWriter(o)), af);
}
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
240
Beispiel: (Reader-/Writer-Klassen)
public class DateiZugriff {
public static
String lesen( String dateiname )
throws FileNotFoundException, IOException
{
BufferedReader in =
new BufferedReader(
new FileReader( dateiname ) );
String line, inputstr = "";
line = in.readLine();
while( line != null ){
inputstr = inputstr.concat( line+"\n");
line = in.readLine();
}
in.close();
return inputstr;
}
public static
void schreiben(String dateiname,String s)
throws IOException
{
PrintWriter out;
out = new PrintWriter(
new FileWriter( dateiname ) );
out.print( s );
out.close();
}
}
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
241
public class DateiZugriffTest {
public static void main( String[] argf ){
String s;
try {
s = DateiZugriff.lesen( argf[0] );
} catch( FileNotFoundException e ){
System.out.println(
"Can't open "+ argf[0] );
return;
} catch( IOException e ){
System.out.println(
"IOException reading "+ argf[0] );
return;
}
try {
DateiZugriff.schreiben("ausgabeDatei",s);
} catch( IOException e ){
System.out.println(
"Can't open "+ argf[0] );
}
}
}
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
242
Input-/Outputstream-Klassen:
DataInput
InputStream
ObjectInput
FileIS
SequenceIS
BufferedIS
PipedIS
ByteArrayIS
FilterIS
CheckedIS
LineNumberIS
ObjectIS
StringBufferIS
DigestIS
InflaterIs
DataIS
PushbackIS
RandomAccessFile
ZipIS
GZIPIS
DataOutput
OutputStream
ObjectOutput
FileOS
PipedOS
ByteArrayOS
ObjectOS
FilterOS
BufferedOS
CheckedOS
DigestOS
InflaterOS
ZipOS
08.01.09
DataOS
PrintStream
GZIPOS
© A. Poetzsch-Heffter, TU Kaiserslautern
243
Bemerkung:
Die Unterscheidung in Reader/Writer einerseits
und Input-/Output-Ströme andererseits wäre
überflüssig, wenn Java parametrische Klassendeklarationen unterstützen würde, bei denen
die Typparameter durch elementare Datentypen
instanziert werden können.
Objektströme
Das Lesen und Schreiben von den Werten der
elementaren Datentypen ist relativ einfach. Sie
besitzen eindeutige Repräsentationen.
Die Ein- und Ausgabe von Objekten ist komplexer:
- Der Zustand reicht zur Repräsentation eines
Objektes nicht aus.
- Objektreferenzen besitzen nur innerhalb des
aktuellen Prozesses eine Gültigkeit.
- Bei Objekten ist häufig ihre Rolle im Objektgeflecht von entscheidender Bedeutung.
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
244
Andererseits ist Ein- und Ausgabe von Objekten
wichtig, um
- Objekte zwischen Prozessen auszutauschen;
- Objekte für nachfolgende Programmläufe
zu speichern, d.h. persistent zu machen.
Beispiel: (Objekte: Wie ausgeben?)
LinkedList ll = new LinkedList();
StringBuffer s = new StringBuffer("Sand");
ll.add("Sich ");
ll.add("mit ");
ll.add(s);
ll.add("alen ");
ll.add("im ");
ll.add(s);
ll.add(" aalen");
Was bedeutet es, das von ll referenzierte Objekt
auszugeben(?):
- nur das LinkedList-Objekt ausgeben;
- das LinkedList-Objekt und die zugehörigen EntryObjekte ausgeben;
- das LinkedList-Objekt, die zugehörigen Entry-Objekte
sowie die String-Objekte und das StringBuffer-Objekt
ausgeben.
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
245
Um Objekte in ihrem Zusammenwirken mit
anderen Objekten wieder einlesen zu können,
müssen sie gemeinsam mit allen erreichbaren
Objekten ausgegeben werden.
Dabei bekommen sie eine Kennung, die relativ zu
den anderen Objekten des Geflechts eindeutig ist.
Wg. möglicher Zyklen ist die Implementierung
der Ausgabe und des Einlesens von Geflechten
nicht einfach. Darum gibt es dafür eine Unterstützung
in der Bibliothek.
Beispiel: (Ausgabe von Objektgeflechten)
Sei die Variable ll wie in obigem Beispiel:
OutputStream os =
new FileOutputStream("speicherndeDatei");
ObjectOutputStream oos =
new ObjectOutputStream( os );
oos.writeObject( ll );
Der Methodenaufruf in der letzten Zeile führt zur
Ausgabe aller von ll aus erreichbaren Objekte in
die Datei mit Namen „speicherndeDatei“.
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
246
Das Einlesen von Objekten und den mit ihnen
abgelegten erreichbaren Objekten birgt eine weitere
Schwierigkeit:
Beim Einlesen müssen Objekte erzeugt werden.
Dafür müssen alle Klassen der einzulesenden
Objekte und geeignete Konstruktoren zur Verfügung
stehen. (Zum Auffinden benutzt Java die Mechanismen
der Reflexion.)
Beispiel: (Einlesen von Objektgeflechten)
LinkedList inll;
InputStream is =
new FileInputStream("speicherndeDatei");
ObjectInputStream ois =
new ObjectInputStream( is );
try {
inll =
(LinkedList)ois.readObject();
} catch( ClassNotFoundException exc ) {
System.out.println("Klasse zu Objekt fehlt");
}
Der Methodenaufruf ois.readObject() liest ein
Objektgeflecht aus der Datei mit Namen
„speicherndeDatei“ ein.
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
247
Zur Beachtung:
• Gibt man ein Objekt mit den erreichbaren Objekten
aus und liest es wieder ein, entsteht eine Kopie.
• Referenziert man von mehreren Variablen Teile des
gleichen Geflechts, kommt es beim Einlesen ggf.
zu mehreren Kopien eines Objekts des ursprünglichen Geflechts.
Beispiel: (Ausgabe u. Einlesen von Objekten)
Ausgabe und Einlesen der von a und c referenzierten
Objekte und Geflechte:
Vor Ausgabe:
a:
b:
Nach Einlesen:
c:
o1:S
o2:V
a:
b:
c:
o11:S
o21:V
204
o3:T
o13:T o14:T
o4:T
o22:T
o5:U
o15:U
o5:U
o23:U
true
true
08.01.09
o4:T
204
true
© A. Poetzsch-Heffter, TU Kaiserslautern
true
248
Begriffsklärung: (Serialisieren)
Serialisieren bedeutet alle von einem Objekt aus
erreichbaren Objekte der Reihe nach in kodierter
Form in einen Strom zu schreiben.
Deserialisieren bezeichnet den umgekehrten
Prozess.
Bemerkung:
• Serialisieren hat zwei zentrale Anwendungen:
- Persistenz von Objekten zu unterstützen;
- Parameterübergabe bei der verteilten objektorientierten Programmen zu realisieren.
• Der Serialisierungsmechanismus muss im Allg.
Zugriff auf private Daten haben und adaptierbar
sein.
• In Java wird die Serialisierbarkeit der Objekte
einer Klasse K dadurch ausgedrückt, dass K die
Schnittstelle Serializable implementiert.
08.01.09
© A. Poetzsch-Heffter, TU Kaiserslautern
249
Herunterladen