Objekte, Klassen, Kapselung, Teil 2

Werbung
Begriffsklärung: (Objektgeflecht)
Attributzugriff
Eine Menge von Objekten, die sich gegenseitig
referenzieren, nennen wir ein Objektgeflecht
(vgl. Folie 339).
Syntax in Java:
Auf Instanzvariablen von Objekten kann mit
Ausdrücken folgender Form zugegriffen werden:
<referenzwertiger Ausdruck> <Attributbezeichner>
Bemerkung:
• Objektgeflechte werden zur Laufzeit aufgebaut
und verändert, sind also dynamische Entitäten.
Semantik:
Werte den referenzwertigen Ausdruck aus. Liefert
dieser null, löse eine NullPointerException aus.
• Klassendiagramme kann man als vereinfachte
statische Approximationen von Objektgeflechten
verstehen.
Andernfalls liefert er die Referenz auf ein Objekt X;
in dem Fall liefert der gesamte Ausdruck die
Instanzvariable von X zum angegenen Attribut (LWert) oder deren Wert (R-Wert).
Lebensdauer von Objekten und
Instanzvariablen:
Abkürzende Notation:
In Java lassen sich Objekte nicht löschen.
Der implizite Methodenparameter this kann beim
Zugriff auf eine Attribut a weggelassen werden, d.h.
Aus Sicht des Programmierers leben Objekte und
deren Instanzvariablen von der Objekterzeugung bis
zum Ende der Ausführung des Programms.
a
ist gleichbedeutend mit
this.a
Der Speicher nicht erreichbarer Objekte wird ggf.
vor Ablauf der Lebensdauer von der automatischen
Speicherbereinigung frei gegeben (vgl. Folien 386ff).
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
innerhalb von Klassen, in denen a deklariert ist.
502
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
503
Methodenaufruf: (engl. method invocation)
Syntax:
Beispiel: (Attributzugriffe)
Ein Methodenaufruf ist ein Ausdruck ähnlich einem
Prozeduraufruf, allerdings mit einem zusätzlichen
Parameter:
class DeinObjekt {
MeinObjekt du;
<refwertigerAusdruck>.<Methodenbezeichner> (
String deinName;
}
<AktuelleParameterliste> )
Semantik:
class MeinObjekt {
boolean binEingetragen;
String
Werte den referenzwertigen Ausdruck aus. Liefert
dieser null, löse eine NullPointerException aus.
Andernfalls liefert er die Referenz auf ein Objekt X.
meinName;
void dichInit (DeinObjekt d) {
System.out.println( d.deinName );
Werte die aktuellen Parameter p1, ..., pn aus.
Führe den Rumpf der angegebenen Methode mit
d.deinName = this.meinName;
- X als implizitem Parameter und
this.binEingetragen = true;
- p1, ... , pn als expliziten Parametern aus.
meinName = d.du.meinName;
}
Das Ergebnis des Aufrufs ist der Rückgabewert der
Ausführung des entsprechenden Methodenrumpfes.
}
Bemerkung:
Eine verfeinerte Semantik wird in 5.3 behandelt.
Dabei wird auch der Zusammenhang zum Senden
von Nachrichten angesprochen.
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
504
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
505
Abkürzende Notation:
Wie beim Attributzugriff kann auch beim Methodenaufruf der implizite Methodenparameter this weggelassen werden, also m(...) statt this.m(...) .
Beispiel: (Methodenaufrufe)
class Mensch {
Mensch vater, mutter;
String name;
Ein objektorientiertes Java-Programm Π besteht aus
einer Menge von Klassen. Mindestens eine der
Klassen muss eine Methode mit Namen main und
folgender Signatur besitzen:
public static void main ( String[] args )
{
...
}
Mensch getOpa (boolean mutterseits) {
if ( mutterseits ) {
Beim Start von Π wird die Klasse angegeben, deren
main-Methode ausgeführt werden soll:
return mutter.vater;
} else {
java <Klassenname> <arg1> <arg2> ...
return vater.vater;
Die Argumente arg1,... werden dabei im Parameter
args als ein Feld von Strings übergeben.
}
}
void eineMethode (Mensch m) {
Mensch opaV;
String opaMName;
opaV = m.getOpa(false);
opaMName =
Objektorientierte Programme
Bei der Ausführung werden die benötigten Objekte
erzeugt. Diese Bearbeiten ihre Aufträge durch
Ausführung von Methoden.
m.getOpa(true). name;
}
...
}
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
506
5.2.3 Spracherweiterungen: Initialisierung
und Ausnahmebehandlung
17.01.2007
507
Beispiel: (Initialisieren von Attributen)
Folgendes Programm mit Initialisierung
Initialisierung
class C {
int ax = 7, ay = 9;
Attribute und lokale Variablen können direkt an
ihrer Deklarationsstelle initialisiert werden.
C(){
m();
}
void m(){
int v1 = 4848, v2 = -3;
}
Somit sind folgende Programmstücke
gleichbedeutend.
float pi;
pi = 3.141;
© A. Poetzsch-Heffter, Universität Kaiserslautern
float pi = 3.141;
...
}
In Java können Attribute und Variablen durch das
Schlüsselwort final als unveränderlich
deklariert werden.
ist äquivalent zu folgendem Programm
class C {
int ax, ay;
In diesem Fall muss die Initialisierung an der
Deklarationsstelle erfolgen.
C(){
ax = 7;
ay = 9;
m();
}
void m(){
int v1, v2;
v1 = 4848; v2 = -3;
}
class Mathe {
...
final float pi = 3.141; // Konstante
...
}
Die Initialisierung von Attributen erfolgt vor dem
Eintritt in den Konstruktorrumpf.
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
...
}
508
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
509
Ausnahmebehandlung
Java bietet Sprachmittel für die Ausnahmebehandlung (engl. exception handling).
Wie in 3.4.1., Folie 235, bereits angesprochen,
kann die Auswertung eines Ausdrucks bzw.
die Ausführung einer Anweisung:
Dabei spielen drei Fragen eine Rolle:
1. Wann/wie werden Ausnahmen ausgelöst?
- normal terminieren
2. Wie kann man sie abfangen?
- in eine Ausnahmesituation kommen und abrupt
terminieren
3. Wie kann man neue Ausnahmetypen deklarieren?
- nicht terminieren
Auslösen von Ausnahmen:
Es gibt drei Arten von Ausnahmesituationen:
1. Vom Programmierer schwer zu kontrollierende
und zu beseitigende Situtationen (Speichermangel)
Das Auslösen einer Ausnahme kann
2. Programmierfehler (Nulldereferenzierung, Verletzung von Indexgrenzen
- oder durch eine Anweisung spezifiziert sein.
In Java gibt es zum Auslösen von Ausnahmen die
throw-Anweisung.
3. Zeitweise nicht verfügbare Ressourcen, anwendungsspezifische Ausnahmen, die behbebbar
sind.
Syntax:
Die throw-Anweisung hat die Form:
<Ausdruck>;
Ausnahmesituationen werden in Programmiersprachen unterschiedlich behandelt:
wobei der Ausdruck ein Ausnahmeobjekt als
Ergebnis liefern muss. (Die throw-Anweisung
entspricht dem raise-Ausdruck in ML.)
- Programmabbruch (engl. abortion)
- Ausnahmebehandlung
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
- sprachdefiniert (z.B. NullPointer, IndexOutOfBounds)
510
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
511
Tritt eine Ausnahme vom Typ A im try-Block auf, wird
ein A-Objekt X erzeugt.
Semantik:
Werte den Ausdruck aus.
Ist der Typ A in der Liste der catch-Klauseln aufgeführt,
Löst die Auswertung eine Ausnahme aus, ist dies
die Ausnahme, die von der Anweisung ausgelöst
wird.
- wird die Ausnahme gefangen,
- X an den Bezeichner der entsprechenden catchKlausel gebunden und
Andernfalls löse die Ausnahme aus, die das
Ergebnis des Ausdrucks ist.
- diese catch-Klausel ausgeführt (Verfeinerung in 5.3).
Abfangen von Ausnahmen:
Benutzerdefinierte Ausnahmetypen:
Die try-catch-Anweisung dient dem Abfangen
und Behandeln von Ausnahmen (entspricht
dem handle-Ausdruck in ML):
Die Deklaration von Exception-Klassen behandeln
wir in Abschnitt 5.3.
Bemerkung:
void myMethod (String[] sfeld)
{
try {
myPrint( sfeld[0] );
myPrint( sfeld[1] );
Java verlangt die Deklaration derjenigen Ausnahmetypen in der Signatur einer Methoden m, die nicht von
m abgefangen werden (Genaueres in 5.3).
Beispiel:
} catch (NullPointerException e) {
myPrintln("sfeld is null");
} catch (IndexOutOfBoundsException e){
myPrintln("sfeld too small");
}
}
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
512
int m( int i ) throws SomeException {
if( i<0 ){
throw new SomeException();
}
...
}
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
513
5.2.4 Anwenden und Entwerfen von Klassen
Beispiel: (Ausnahmebehandlung)
class Try {
long maxint = 2147483647L;
Klassen bilden das zentrale Sprachkonstrukt von Java.
Dementsprechend stehen beim Programmentwurf
zwei Fragen im Mittelpunkt:
try{
•
Welche existierenden Klassen können für den
Programmentwurf herangezogen werden?
m = Integer.parseInt( argf[0] );
•
Welche Klassen müssen neu entworfen werden?
n = Integer.parseInt( argf[1] );
Zur Diskussion dieser Aspekte betrachten wir ein
kleines Beispiel.
public static void main( String[] argf ){
int m, n, ergebnis = 0 ;
long aux = (long)m + (long)n;
if( aux > maxint ) throw
ergebnis =
new Ueberlauf();
(int)aux ;
} catch ( IndexOutOfBoundsException e ) {
System.out.println("Falsche Argumente");
} catch ( NumberFormatException e ) {
System.out.println(
"Element in argf keine int-Konstante");
} catch ( Ueberlauf e ) {
Aufgabenstellung:
Ein rudimentäres Browser-Programm soll realisiert
werden, mit dem einfache W3Seiten bei einem
Server geholt und in einem Fenster angezeigt
werden können.
Wir gehen davon aus, dass die folgenden Klassen
existieren:
- W3Seite: Implementiert W3Seiten.
System.out.println("Ueberlauf");
- W3Server: Implementiert W3Server bzw. ihre
Schnittstelle.
}
}
- Textfenster: Kann W3-Seiten anzeigen.
© A. Poetzsch-Heffter, Universität Kaiserslautern
17.01.2007
514
Klassendiagramm zur Lösung der Aufgabenstellung:
*
Browser
aktSeite: W3Seite
laden(...)
*
W3Seite
Textfenster
anzeigen(...)
515
/**
* Objekte repräsentieren triviale
* Web-Seiten mit Titelzeile und Inhalt
*/
class W3Seite {
String titel;
String inhalt;
ablegen(String,W3Seite)
W3Seite holen(String)
1
1.. *
© A. Poetzsch-Heffter, Universität Kaiserslautern
Die vollständige Klasse W3Seiten:
* W3Server
1
17.01.2007
String getTitel()
String getInhalt()
W3Seite ( String t, String i ) {
titel = t;
this.inhalt = i;
}
Schnittstellen der gegebenen Klassen:
class W3Server {
W3Server() { ... }
void ablegenSeite( String adr, W3Seite s ){
...
}
W3Seite holenSeite( String adr ) { ... }
}
String getTitel() {
return this.titel;
}
String getInhalt() {
return inhalt;
}
}
class TextFenster ... {
...
TextFenster() { ... }
void anzeigen(String tzeile,String text){
...
}
}
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
516
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
517
5.2.5 Spracherweiterungen: Überladen,
Klassenvariablen und -methoden
Wichtige Implementierungsteile einer rudimentären
Browser-Klasse:
class Browser {
W3Server
Überladen
meinServer;
TextFenster oberfl;
W3Seite
aktSeite;
In Java ist es erlaubt, innerhalb einer Klasse mehrere
Methoden mit dem gleichen Namen zu deklarieren,
d.h. es gibt zwei Bindungen mit gleichem Namen.
// aktuelle Seite
Browser( W3Server server ){
Eine derartige Mehrfachverwendung nennt man
Überladen eines Namens. Methoden mit gleichen
Namen müssen sich in der Anzahl oder in den Typen
der Parameter unterscheiden.
meinServer = server;
oberfl
= new TextFenster();
laden( new W3Seite("Startseite",
"NetzSurfer: Keiner ist kleiner") );
Durch die unterschiedliche Signatur kann der
Übersetzer die Überladung auflösen, d.h. für jede
Aufrufstelle ermitteln, welche von den Methoden
gleichen Namens an der Aufrufstelle gemeint ist.
interaktiveSteuerung();
}
void laden( W3Seite s ){
aktSeite = s;
oberfl.anzeigen( aktSeite.getTitel(),
Entsprechend dem Überladen von Methodennamen
erlaubt Java auch das Überladen bei Konstruktoren.
aktSeite.getInhalt());
}
Beispiel: (Überladen)
void interaktiveSteuerung() { ... }
}
Die Java-Bibliothek bietet viele Beispiele für
Überladung. Wir betrachten die Klasse String
(hier nur unvollständig wiedergegeben):
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
class String {
/** The value is
char[] value;
/** The offset
int offset;
/** The count
int count;
used for character storage
518
© A. Poetzsch-Heffter, Universität Kaiserslautern
519
Klassenattribute und Klassenmethoden
*/
is the first index of the used storage*/
is the number of characters in the ...
17.01.2007
Die Deklaration eines Klassenattributs liefert eine
klassenlokale Variable. Syntax:
*/
static
<Typausdruck> <Attributname> ;
Klassenattribute/-variablen werden häufig auch als
statische Attribute/Variablen bezeichnet.
String() { value = new char[0]; }
String( String value ) { ... }
String( char[] value ) {
this.count = value.length;
this.value = new char[count];
System.arraycopy(value,0,this.value,0,count);
}
...
int
int
int
int
indexOf(int ch) { return indexOf(ch, 0);}
indexOf(int ch, int fromIndex) { ... }
indexOf(String str) { ...}
indexOf(String str, int fromIndex) {...}
int length() { return count; }
char charAt(int index) {
if ((index < 0) || (index >= count)) {
throw
new StringIndexOutOfBoundsException(index);
}
return value[index + offset];
}
}
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
520
Die Variable kann innerhalb der Klasse mit dem
Attributnamen, außerhalb mittels
<Klassenname> . <Attributname>
angesprochen werden. Die Lebensdauer der
Variablen entspricht der Lebensdauer der Klasse.
Bemerkung:
Klassenvariablen verhalten sich ähnlich wie globale
Variablen in der prozeduralen Programmierung.
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
521
Beispiel: (Klassenattribut)
Beispiel: (Klassen-, statische Methoden)
class InstanceCount {
static int instCount = 0;
Deklaration:
class String {
...
static String valueOf( long l ) { ... }
static String valueOf( float f ) { ... }
...
}
InstanceCount(){
instCount++;
...
} ...
}
Anwendung/Aufruf:
String.valueOf( (float)(7./9.) )
Die Deklaration einer Klassenmethode entspricht
der Deklaration einer Prozedur. Klassenmethoden
besitzen keinen impliziten Parameter. Sie können
nur auf Klassenattribute, Parameter und lokale
Variable zugreifen. Syntax:
liefert die Zeichenreihe:
static <Methodendeklaration>
"0.7777778"
Bemerkung:
Klassenmethoden werden häufig auch als
statische Methoden bezeichnet.
In Kapitel 4 wurden Klassenmethoden zur
prozeduralen Programmierung in Java genutzt.
Klassenmethoden werden mit folgender Syntax
aufgerufen:
<Klassenname> . <Methodenname> ( ... )
Innerhalb der Klasse kann der Klassenname entfallen.
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
522
© A. Poetzsch-Heffter, Universität Kaiserslautern
523
2. Unsere Klasse InputOutput liefert auch schöne
Beispiele für statische Methoden und Überladung:
Beispiele: (Klassenattribute u. -methoden)
1. Charakteristische Beispiele für Klassenattribute und
-methoden liefert die Klasse System, die eine Schnittstelle von Programmen zur Umgebung bereitstellt:
class System {
final static InputStream in
17.01.2007
public class InputOutput {
public static int readInt(){...}
public static String readString(){...}
public static char readChar(){...}
public static void print(int i){
System.out.print(i);
}
public static void println(int i){
System.out.println(i);
}
public static void print(char c){
System.out.print(c);
}
public static void println(char c){
System.out.println(c);
}
public static void print(String s){
System.out.print(s);
}
public static void println(String s){
System.out.println(s);
}
= ...;
final static PrintStream out = ...;
static void exit(int status) { ... }
static native void arraycopy(
Object src,int src_position,
Object dst,int dst_position, int length);
}
Die Klasse PrintStream besitzt Methoden print und
println:
System.out.print("Das klaert die Syntax");
System.out.println(" von Printaufrufen");
}
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
524
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
525
5.2.6 Zusammenwirken der
Spracherweiterungen
Entwurf der Implementierung:
Das Zusammenwirken der eingeführten Sprachelemente erlaubt bereits, recht komplexe Programme
zu schreiben.
- Die gemeinsamen Teile aller Browserfenster
werden durch Klassenattribute und –methoden
realisiert.
- Es gibt zwei Konstruktoren: Einer startet das
erste Browserobjekt; der andere weitere
Browserobjekte.
Folgendes Programmbeispiel mischt prozedurale
und objektorientierte Sprachelemente. Es dient
zum Studium des Zusammenwirkens der
Spracherweiterungen.
- Die gemeinsamen Teile der Konstruktoren
werden von der Methode initialisieren
erledigt.
Beispiel: (Zusammenwirken von Sprachel.)
- Die interaktive Steuerung von der Konsole
wird durch eine statische Methode implementiert.
- Zur einfacheren Handhabung steht eine
Klassenmethode start zur Verfügung, die den
W3Server als Argument bekommt:
Wir erweitern das Browserbeispiel von 5.2.4:
- Unterstützung mehrerer Browserfenster
...
Browser.start( testServer );
...
- Interaktive Steuerung über die Konsole
class Konsole {
static String readString() { ... }
static void writeString( String s ) {...}
- Die Browserfenster werden in einem Feld auf
Klassenebene verwaltet.
}
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
526
class Browser {
TextFenster oberfl;
W3Seite
aktSeite;
static W3Server meinServer;
static final int MAX_ANZAHL = 4;
static Browser[] gestarteteBrowser =
new Browser[MAX_ANZAHL];
static int
naechsterFreierIndex = 0;
static int
aktBrowserIndex;
static W3Seite
startseite =
new W3Seite("Startseite",
"NetzSurfer: Keiner ist kleiner");
// Konstruktor für ersten Browsers
Browser( W3Server server ) {
if( naechsterFreierIndex != 0 ) {
System.out.println("Browser gestartet");
} else {
meinServer = server;
initialisieren();
}
}
// Konstruktor für weiterere Browserfenster
Browser() {
if( naechsterFreierIndex == MAX_ANZAHL ) {
System.out.print("Maximale Anzahl ");
System.out.println(" Browser erreicht");
} else {
initialisieren();
}
}
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
528
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
527
static void start( W3Server server ) {
new Browser(server);
Browser.interaktiveSteuerung();
}
void initialisieren() {
oberfl
= new TextFenster();
gestarteteBrowser[ naechsterFreierIndex ]
= this;
aktBrowserIndex = naechsterFreierIndex;
naechsterFreierIndex++ ;
laden( startseite );
}
void laden( W3Seite s ){
aktSeite = s;
oberfl.anzeigen(aktSeite.getTitel(),
aktSeite.getInhalt());
}
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
529
static void interaktiveSteuerung() {
char steuerzeichen = '0';
do {
Konsole.writeString("Steuerzeichen [lnwe]: ");
try {
String eingabe = Konsole.readString();
if( eingabe.equals("") )
steuerzeichen = '0';
else
steuerzeichen = eingabe.charAt(0);
} catch( Exception e ) {
System.exit( 0 );
}
switch( steuerzeichen ){
case 'l':
String seitenadr;
Konsole.writeString("Seitenadresse: ");
seitenadr = Konsole.readString();
gestarteteBrowser[aktBrowserIndex] .
laden( meinServer.holenSeite( seitenadr ) );
break;
case 'n': new Browser(); break;
case 'w':
aktBrowserIndex =
(aktBrowserIndex+1) % naechsterFreierIndex;
break;
case 'e':
System.exit( 0 );
default:
Konsole.writeString("falsche Eingabe\n");
}
} while( true );
}
}
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
530
Prozedurale Datenstrukturen:
5.2.7 Rekursive Klassen
Definition: (rekursive Klassendeklarationen)
Eine Klassendeklaration K heißt direkt rekursiv,
wenn Attribute von K den Typ K haben.
Eine Menge von Klassendeklarationen heißt
verschränkt rekursiv oder indirekt rekursiv
(engl. mutually recursive), wenn die Deklarationen
gegenseitig voneinander abhängen.
Eine Klassendeklaration heißt rekursiv, wenn
sie direkt rekursiv ist oder Element einer Menge
verschränkt rekursiver Klassendeklarationen ist.
Bemerkung:
• Wir identifizieren Klassen mit ihren Deklarationen.
• Wichtige Anwendung rekursiver Klassen ist die
Implementierung von Listen-, Baum- und Graphstrukturen.
Implementierung von Listen
Im Folgenden betrachten wir rekursive Klassen
für Listen. Dabei variieren wir die Programmierstile
und die bereitgestellten Schnittstellen.
17.01.2007
class ProcListMain
Beispiel: (Einfachverkettete Listen)
Bei einfachverketteten Listen gibt es für jedes
Listenelement ein Objekt mit zwei Instanzvariablen:
{
public static void main( String[] argf ){
- zum Speichern des Elements
ProcList l1 = new ProcList();
ProcList l2 = new ProcList();
ProcList l3 = new ProcList();
- zum Speichern der Referenz auf den Rest der Liste.
: ProcList
: ProcList
: ProcList
head:
tail:
head: -3
tail:
head: 84
tail:
l1.head
l2.head
l3.head
l1.tail
l2.tail
l3.tail
© A. Poetzsch-Heffter, Universität Kaiserslautern
=
=
=
=
=
=
1;
2;
3;
l2;
l3;
null;
System.out.println( sortiert(l1) );
l3.head = 0;
System.out.println( sortiert(l1) );
class ProcList {
int head;
ProcList tail;
}
17.01.2007
531
static boolean sortiert(ProcList l){
if( l == null || l.tail == null ) {
return true;
} else if( l.head <= l.tail.head ){
return sortiert( l.tail );
} else {
return false;
}
}
In der prozeduralen Programmierung sind
Datentypen und Prozeduren zunächst getrennt
(Zusammenfassung erst auf Modulebene).
6
© A. Poetzsch-Heffter, Universität Kaiserslautern
}
}
532
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
533
Beispiel: (Zugriff nur über Methoden)
Diskussion:
class FunctionalList {
private int head;
private FunctionalList tail;
Die prozedurale Fassung erlaubt es jedem,
der eine Referenz auf ein Listenknoten hat,
das Objektgeflecht unkontrolliert zu verändern.
static FunctionalList empty() {
return new FunctionalList();
}
boolean isempty(){
return tail == null;
}
int head(){
if( isempty() ){
throw new NoSuchElementException();
}
return head;
}
FunctionalList tail(){
if( isempty() ){
throw new NoSuchElementException();
}
return tail;
}
FunctionalList cons( int i ) {
FunctionalList aux = new FunctionalList();
aux.head = i;
aux.tail = this;
return aux;
}
Zum Beispiel könnte man Listen in ein zyklisches
Geflecht verändern und damit Invarianten
verletzen.
Funktionale Datenstrukturen:
Unterbindet man den beliebigen Zugriff auf
die Attribute und bietet nur Methoden an, um
Listen auf- und abzubauen, kann man ein
Verhalten wie in der funktionalen Programmierung
erreichen.
Das Mehr an Garantien wird durch weniger
Flexibilität bezahlt. Insbesondere ist das direkte
Einfügen und Modifizieren in der „Mitte“ einer
Datenstruktur nicht mehr möglich.
}
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
class FunctionalListMain
534
{
public static void main( String[] argf ){
FunctionalList le, l1, l2, l3;
le = FunctionalList.empty();
l3 = le.cons(3);
l2 = l3.cons(2);
l1 = l2.cons(1);
System.out.println( sortiert(l1) );
l1 = l3.cons(4);
System.out.println( sortiert(l1) );
}
535
Zunächst die Schnittstelle und Anwendung der Klasse:
class SLinkedList {
// Liefert das erste Element der Liste,
// ohne diese Liste zu veraendern
int getFirst(){ ... }
// Fügt vorne ein neues Element an diese
// Liste an
void addFirst( int n ) { ... }
// Löscht das erste Element dieser Liste
// und liefert es als Ergebnis
int removeFirst() { ... }
// Liefert die Elementanzahl dieser Liste
int size() { ... }
}
class SLinkedListMain {
public static void main( String[] argf ){
SLinkedList l = new SLinkedList();
l.addFirst(3);
l.addFirst(2);
l.addFirst(1);
System.out.println( l.removeFirst() );
System.out.println( l.size() );
System.out.println( l.removeFirst() );
}
}
}
Objektorientierte Listen:
Aus objektorientierter Sicht ist eine Liste ein Behälter,
in den man etwas hineintun und aus dem man etwas
herausnehmen kann:
© A. Poetzsch-Heffter, Universität Kaiserslautern
© A. Poetzsch-Heffter, Universität Kaiserslautern
Beispiel: (Liste als Behälter)
static boolean sortiert( FunctionalList l ){
if( l.isempty() || l.tail().isempty() ) {
return true;
} else if( l.head() <= l.tail().head() ){
return sortiert( l.tail() );
} else {
return false;
}
}
17.01.2007
17.01.2007
536
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
537
Und nun Teile der Implementierung von SLinkedList:
Problem bei der erläuterten Implementierung:
Rekursives oder iteratives Durchlaufen durch die
Liste ist nicht möglich (Abhilfe: s.u.).
import java.util.NoSuchElementException;
Andere Formen von Listenimplementierungen
speichern die Elemente in Feldern oder nutzen eine
doppelte Verkettung der Eintragsknoten:
class SEntry {
int head;
SEntry tail;
}
class SLinkedList {
:LinkedList
private SEntry entries = null;
private int size = 0;
header:
size: 3
: Entry
int getFirst(){
if( size == 0 ){
throw new NoSuchElementException();
}
return entries.head;
}
element:
next:
previous:
void addFirst( int n ) {
size++;
SEntry auxe = new SEntry();
auxe.head = n;
auxe.tail = entries;
entries
= auxe;
}
...
: Entry
: Entry
element:
next:
previous:
element:
next:
previous:
}
...
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
538
17.01.2007
Implementierung von Bäumen
: Entry
element:
next:
previous:
...
...
© A. Poetzsch-Heffter, Universität Kaiserslautern
539
boolean contains( int e ) {
if( e < elem && left != null ) {
return left.contains(e);
} else if( elem > e && right != null ) {
return right.contains(e);
} else {
return e==elem;
}
}
Binäre Bäume sind wichtige Datenstrukturen, z.B.
zur Darstellung von Mengen.
Beispiel: (Bäume)
class BinTree {
private int elem;
private BinTree left, right;
void printTree() {
if( left != null ) left.printTree();
System.out.println(elem);
if( right != null ) right.printTree();
}
BinTree( int e ) {
elem = e;
}
}
void sorted_insert( int e ) {
if( e < elem ) {
if( left == null ) {
left = new BinTree(e);
} else {
left.sorted_insert(e);
}
} else if( elem < e ) {
if( right == null ) {
right = new BinTree(e);
} else {
right.sorted_insert(e);
}
}
}
// weiter auf nächster Folie
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
class BinTreeMain {
public static void main( String[] argf ){
BinTree bt = new BinTree(12);
bt.sorted_insert(3);
bt.sorted_insert(12);
bt.sorted_insert(11);
bt.sorted_insert(12343);
bt.sorted_insert(-2343);
bt.sorted_insert(233);
bt.printTree();
}
}
540
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
541
Iteratoren
class SLinkedList {
private SEntry entries = null;
...
Iterator iterator() {
return new Iterator( entries );
}
}
Iteratoren erlauben es, schrittweise über BehälterDatenstrukturen zu laufen, so dass alle Elemente
der Reihe nach besucht werden. Im Zusammenhang
mit Kapselung (s.u.) sind sie unverzichtbar.
Beispiel: (Iteratoren)
class SLinkedListMain {
public static void main( String[] argf ){
SLinkedList l = new SLinkedList();
l.addFirst(3);
l.addFirst(2);
l.addFirst(4);
Wir reichern die Klasse SLinkedList mit Iteratoren
an und zeigen deren Anwendung.
class Iterator {
SEntry current;
Iterator( SEntry se ) {
current = se;
}
Iterator iter = l.iterator();
while( iter.hasNext() ) {
System.out.println( iter.next() );
}
boolean hasNext() {
return current!=null;
}
}
}
int next() {
if( current==null ){
throw new NoSuchElementException();
}
int res = current.head;
current = current.tail;
return res;
}
Bemerkung:
Der Iterator muss Zugriff auf die interne Repräsentation
der Datenstruktur haben, über die er iteriert.
}
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
542
5.2.8 Typsystem von Java und
parametrische Typen
© A. Poetzsch-Heffter, Universität Kaiserslautern
17.01.2007
543
Bemerkung:
In typisierten objektorientierten Sprachen können
Werte zu mehreren Typen gehören (Subtypen).
Typsystem von Java
Werte in Java sind
- die Elemente der elementaren Datentypen,
Parametrische Typen
- Referenzen auf Objekte,
Jeder Wert in Java gehört zu mindestens einem Typ.
Vordefinierte und benutzerdeklarierte Typen sind
Auch objektorientierte Sprachen unterstützen
parametrische Typsysteme wie in ML. Für Java ist
eine derartige Unterstützung ab Version 1.5
verfügbar.
- die vordefinierten elementaren Typen,
Beispiel: (Parametrische Typen)
- der Wert null.
- die durch Klassen deklarierten Typen,
Wir betrachten eine parametrische Fassung der
Klasse SLinkedList:
- die durch Schnittstellen deklarierten Typen (s.u.).
Implizit deklariert sind die Feldtypen zu den Klassenund Schnittstellentypen (Typkonstruktor „[]“ ).
class SLinkedList<A> {
A getFirst(){ ... }
Feld-, Klassen- und Schnittstellentypen fasst man
unter dem Namen Referenztypen zusammen.
(null gehört zu allen Referenztypen.)
void addFirst( A n ) { ... }
A removeFirst() { ... }
Klassen- und Schnittstellentypen beschreiben, welche
Nachrichten ihre Objekte verstehen bzw. welche
Methoden sie besitzen.
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
544
int size() { ... }
}
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
545
In Java 5 ist die Instanzierung der TypParameter nur durch Referenztypen gestattet:
class Test {
public static void main( String[] argf ){
SLinkedList<String> l
= new SLinkedList<String>();
l.addFirst("Die Ersten werden");
l.addFirst("die Letzten sein");
int i = l.getFirst().indexOf("sein");
// liefert 13
SLinkedList<W3Seite> l
= new SLinkedList<W3Seite>();
l.addFirst(new W3Seite("Titel","Inhalt");
l.addFirst(new W3Seite("Title","Content");
int i = l.getFirst().indexOf("sein");
// liefert Übersetzungsfehler
}
}
17.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
546
Herunterladen