• Keine Ergebnisse gefunden

•• Weitere Aspekte der Subtypbildung Aspekte der Subtypordnung

N/A
N/A
Protected

Academic year: 2022

Aktie "•• Weitere Aspekte der Subtypbildung Aspekte der Subtypordnung"

Copied!
5
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

29.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 594

Weitere Aspekte der Subtypbildung

Dieser Unterabschnitt behandelt detailliertere Aspekte zu

- der Subtypordnung

- Typtest und Typkonvertierungen - Polymorphie

Aspekte der Subtypordnung

Zyklenfreiheit:

Die Subtyprelation darf keine Zyklen enthalten (sonst wäre sie keine Ordnung). Folgendes Fragment ist also in Java nicht zulässig:

interface C extends A { ... } interface B extends C { ... } interface A extends B { ... } Subtyprelation bei Feldern:

Jeder Feldtyp mit Komponenten vom Typ S ist ein Subtyp von Object: S[] ≤ Object . D.h. folgende Zuweisung ist zulässig:

Object ov = new String[3] ;

29.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 595

Ist S≤T, dann ist S

≤ T

. D.h. folgende Zuweisung ist zulässig:

Person[] pv = new Student[3] ; Diese Festlegung der Subtypbeziehung

zwischen Feldtypen ist in vielen Fällen praktisch.

Problem:

Statische Typsicherheit ist nicht mehr gegeben:

String[] strfeld = new String[2];

Object[] objfeld = strfeld;

objfeld[0] = new Object(); //Laufzeitfehler // ArrayStoreException int strl = strfeld[0].length();

Speicherzustand nach der zweiten Zeile:

strfeld:

objfeld:

:String[]

2 length:

0:

1:

Subtypen und elementare Datentypen:

Zwischen den elementaren Datentypen und den Referenztypen gibt es keine Subtypbeziehung:

int Object , int boolean , double int d.h. die folgenden Zuweisungen sind unzulässig:

Object ov = 7;

boolean bv = 9;

int iv = 3.4;

Wie in ML gibt es auch in Java die Möglichkeit, Werte eines elementaren Datentyps in Werte eines anderen Datentyps zu konvertieren (siehe unten).

Der Zusammenhang zwischen elementaren Datentypen und Referenztypen wird in Java über sogenannte Wrapper-Klassen erzielt.

Ein Wrapper-Objekt für den elementaren Datentyp D besitzt ein Attribut zur Speicherung von Werten des Typs D.

Anwendung von Wrapper-Klassen:

Integer ist die Wrapper-Klasse für den Typ int:

Integer iv = new Integer(7);

Object ov = iv;

int n = iv.intValue() + 23 ;

Character

Number

BigInteger BigDecimal Comparable

Object

Javas Wrapper-Klassen sind im Paket java.lang definiert. Folgendes Diagramm zeigt die Subtyp- beziehungen:

Byte Short

Integer Boolean

Long Float Double

(2)

29.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 598

public interface Comparable { public int compareTo(Object o);

}

public class Main {

static boolean issorted( Comparable[] cf ) { int i;

if( cf.length<2 ) return true;

for( i=0; i<cf.length-1; i++) {

if( cf[i].compareTo(cf[i+1]) > 0 ) { return false;

} }

return true;

}

public static void main( String[] args ) { boolean b;

Character[] cfv = new Character[4];

cfv[0] = new Character('\'');

cfv[1] = new Character('Q');

cfv[2] = new Character('a');

cfv[3] = new Character('b');

b = issorted(cfv);

System.out.println("cfv sortiert: " + b );

} }

29.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 599

Typtest und Typkonvertierungen:

Werte eines elementaren Datentyps lassen sich mittels sogenannter Casts in Werte anderer Datentypen konvertieren:

double dv = 3333533335.3333333;

// dv == 3.3335333353333335E9 float fv = (float) dv;

// fv == 3.33353344E9 long lv = (long) fv;

// lv == 3333533440L int iv = (int) lv;

// iv == -961433856 short sv = (short) iv;

// sv == -20736 byte bv = (byte) sv;

// bv == 0

Typkonvertierungen von Datentypen mit kleinerem Wertebereich in solche mit größerem Wertebereich werden automatisch durchgeführt:

3.4 + 7 ist äquivalent zu:

3.4 + (double) 7

29.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 600

Bei Referenztypen prüft ein Cast, ob das geprüfte Objekt zu dem entsprechenden Typ gehört:

- falls ja, wird die Ausführung fortgesetzt;

- falls nein, wird eine ClassCastException ausgelöst.

Number nv = new Integer(7);

Object ov = (Object) nv; // upcast Number nv1 = (Object) nv; //

Integer iwv = nv; //

Integer iwv1 = (Integer) nv; // downcast Float fwv = (Float) nv; //

Comparable c = (Comparable) nv; //

String sv = (String) nv; //

Beispiel: (Konvertieren von Referenztypen)

Java bietet außerdem den Operator instanceof zum Typtesten an:

Comparable c;

if( nv instanceof Comparable ) { c = (Comparable) nv;

} else {

throw new ClassCastException();

}

29.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 601

Polymorphie:

In Kapitel 3 (Folie 185) hatten wir Polymorphie wie folgt erklärt:

Ein Typsystem heißt polymorph, wenn es Werte bzw.

Objekte gibt, die zu mehreren Typen gehören.

Begriffsklärung: (Subtyp-Polymorphie)

Die Form der Polymorphie in Typsystemen mit Subtypen heißt Subtyp-Polymorphie.

Bemerkung:

Casts sollten soweit möglich vermieden werden.

Beispiel: (inhomogene Listen)

LinkedList ls = new LinkedList();

ls.addLast("letztes Element");

((String) ls.getLast()).indexOf("Elem");

// liefert 8 ls.addLast( new Float() );

// kein Uebersetzungsfehler ((String) ls.getLast()).indexOf("Elem");

// Laufzeitfehler

(3)

29.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 602

Vergleich von Subtyp- und parametrischer Polymorphie:

- Subtyp-Polymorphie:

ermöglicht inhomogene Datenstrukturen;

benötigt keine Instanzierung von Typparametern;

ist sehr flexibel in Kombination mit dynamischer Methodenauswahl.

- parametrische Polymorphie:

vermeidet Laufzeitprüfungen bei homogenen Datenstrukturen (effizienter);

bietet mehr statische Prüfbarkeit (keine Ausnahmen zur Laufzeit).

Beispiel: (Parametrische Listen)

LinkedList<String> ls =

new LinkedList<String>();

ls.addLast("letztes Element");

ls.getLast()).indexOf("Elem"); // liefert 8 ls.addLast( new Float() );

// Übersetzungsfehler!

ls.getLast().indexOf("Elem");

29.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 603

Programmieren mit Schnittstellentypen

Wir demonstrieren die Anwendung von Schnittstellen- typen an zwei charakteristischen Beispiel:

1. Implementierungen einer abstrakten Datenstruktur mit unterschiedlichen Laufzeit- und Speichplatz- eigenschaften:

- Der Anwender der Datenstruktur wählt die Eigenschaften bei der Erzeugung aus.

- Ansonsten benutzt die Anwendung nur die Methoden der Schnittstelle.

Drei Implementierungen für Dictionary 2. Der Anwender eines Objekts kennt nur den

Schnittstellentyp des Objekts, aber nicht dessen Implementierung:

Beobachtermuster

Drei Implementierungen von Dictionary:

In 3.2.2 wurden natürliche Suchbäume zur Realisierung der abstrakten Datenstruktur DICTIONARY betrachtet.

Hier behandeln wir drei andere Suchverfahren:

- A. Binäre Suche in Feldern - B. Balancierte Suchbäume - C. Hashing/Streuspeicherung

Dabei basiert die Verwaltung von Datensätzen auf drei Grundoperationen:

- Einfügen eines Datensatzes in eine Menge von Datensätzen;

- Suchen eines Datensatzes mit Schlüssel k;

- Löschen eines Datensatzes mit Schlüssel k.

In vereinfachter Anlehnung an java.util.Dictionary legen wir folgende Schnittstelle zugrunde:

interface Dictionary { Object get( int key );

void put( int key, Object value );

void remove( int key );

}

Ziel ist es, Datenstrukturen zu finden, bei denen der Aufwand für obige Operationen gering ist.

Entsprechend der Signatur vonput gehen wir im Folgenden davon aus, dass ein Datensatz aus einem Schlüssel und einer Referenz vom TypObjectbesteht.

class DataSet { int key;

Object data;

DataSet(int k,Object d){ key=k; data=d; } }

Wir betrachten drei Implementierungen des SchnittstellentypesDictionaryund präsentieren jeweils

- die Datenstruktur

- die drei grundlegenden Operationen - eine einfache Komplexitätsabschätzung.

A. Binäre Suche in Feldern

Lineare Datenstrukturen (Listen, Felder) mit einem Zugriff über den Komponentenindex erlauben das Auffinden eines Datensatzes durch binäre Suche.

(Hier betrachten wir eine Realisierung mit Feldern ähnlich wie AList aus der Übung.)

Datenstruktur:

Ein Dictionary wird repräsentiert durch ein Objekt mit:

- einer Referenz auf das Feld mit den Datensätzen - der Größenangabe des Feldes (capacity)

- der Anzahl der gespeicherten Datensätze (size)

(4)

29.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 606

public class ArrayDictionary implements Dictionary {

private DataSet[] elems;

private int capacity;

private int size;

public ArrayDictionary() { elems = new DataSet[8];

capacity = 8;

size = 0;

} ...

}

Die Operationen gewährleisten folgende Invariante:

- Die Datensätze sind aufsteigend sortiert.

- Die Schlüssel sind eindeutig.

private int searchIndex( int key ) { /* liefert Index ix von Datensatz mit

Schlüssel k, wobei gilt:

- k == key, wenn so ein Eintrag vorhanden - k ist nächst größere Schlüssel als key,

zu dem Eintrag vorhanden size, sonst

*/ ...

}

Zum Einfügen, Suchen und Löschen benötigt man den Index, an dem die Operation ausgeführt werden soll:

29.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 607

Heraussuchen:

Löschen:

public Object get( int key ) { int ix = searchIndex( key );

if( ix == size || elems[ix].key != key ){

return null;

} else {

return elems[ix].data;

} }

public void remove( int key ) { int ix = searchIndex( key );

if( ix!=size && elems[ix].key == key ){

/* Datensatz löschen */

for( int i = ix+1; i<size; i++ ) { elems[i-1] = elems[i];

} size--;

} }

Bemerkung:

Bei den Operationen ist eine schnelle Suche wichtig.

Deshalb konzentrieren sich die algorithmischen Untersuchungen auf diese Operation.

29.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 608

public void put( int key, Object value ) { int ix = searchIndex( key );

if( ix == size || elems[ix].key > key ) { /* neuen Datensatz eintragen */

size++;

if( size > capacity ) { DataSet[] newElems =

new DataSet[2*capacity];

for( int i = 0; i<ix; i++ ) { newElems[i] = elems[i];

}

for( int i = ix+1; i<size; i++ ) { newElems[i] = elems[i-1];

}

newElems[ix] = new DataSet(key,value);

elems = newElems;

capacity = 2*capacity;

} else {

for( int i = size-1; i>=ix+1; i-- ) { elems[i] = elems[i-1];

}

elems[ix] = new DataSet( key, value );

}

} else { // elems[ix].key == key elems[ix].data = value;

} }

Einfügen

29.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 609

private int searchIndex( int key ) {

if( size==0 || elems[size-1].key < key ){

return size;

} else { int ug = 0;

int og = size-1;

/* key <= elems[og].key */

while( ug<=og-2 ) {

int mid = ug + (og-ug)/2;

if( key < elems[mid].key ) { og = mid;

} else { ug = mid;

} }

if( elems[ug].key < key ) { return og;

} else { return ug;

} } }

Suchen

Das Arbeiten mit sortierten Feldern ermöglicht binäre Suche:

- Durch Vergleich mit Schlüssel des Datensatzes in der Feldmitte kann bestimmt werden, ob der gesuchte Satz in der unteren oder oberen Hälfte des Feldes liegt.

- Suche in der bestimmten Hälfte weiter.

(5)

29.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 610

Diskussion:

Binäres Suchen verursacht logarithmischen Aufwand: O(log N). Ebenso das Herausholen eines Eintrags aus dem ArrayDictionary.

Einfügen und Löschen benötigen in der gezeigten Variante linearen Aufwand: O(N).

Vorteile:

- einfach und speichersparend zu realisieren - schnelles Heraussuchen von Einträgen Nachteile:

- Einfügen und Löschen sind vergleichsweise langsam.

B. Balancierte Suchbäume

In 3.2.2 haben wir natürliche binäre Suchbäume betrachtet. Sofern binäre Suchbäume hinreichend gut ausgeglichen (balanciert) sind, ist der Aufwand aller drei Grundoperationen logarithmisch.

Ziel ist es, bei den modifizierenden Operationen den Baum wenn nötig wieder auszubalancieren.

(Wir betrachten hier nur das ausbalancieren nach Einfüge-Operationen.)

Referenzen

ÄHNLICHE DOKUMENTE

Das Verfahren integrierte Planung im Dialog Die Vorgehensweise setzte auf ein integriertes, Ergebnis orientiertes Verfahren und knüpfte dabei an vorhandene Untersuchungen, Planungen

Eine selbstorganisierende Liste ist eine Liste, bei der kurz aufeinanderfolgende Zugriffe auf dieselben Elemente sehr effizient sind: Wird ein Element in der Liste gefunden,

[r]

[r]

In dieser Aufgabe sollt ihr ein wenig mit dem Datentyp int vertraut werden.. Dazu entwi- ckeln wir

Eingabeaufforderung und Einlesen der Zahl z1 Eingabeaufforderung und Einlesen der Zahl z2 Berechne z1 + z2 und Ausgabe des Ergebnises Berechne z1 - z2 und Ausgabe des

In den folgenden Kommentaren bezeichnen die dop- pelten oder dreifachen Namen Variablen gleichen Namens in geschachtelten Scopes.. (a)

Geschäftstätigkeit Ziel/Kompetenzen: Chancen und Risiken des Außenhandels einschätzen können, Vertragsbestandteile int. Verträge erläutern können, Abwicklung von