10 Interfaces 10 - 1 Interfaces Inhalt Interfaces Abstrakte Klassen Enumeration Übungsaufgaben Interfaces In C++ kann eine Klasse Kind von zwei Eltern sein. Dieses Konzept der mehrfachen Vererbung wurde in Java nicht übernommen, da es zu viele Schwierigkeiten mit sich bringt. Statt dessen kann eine Klasse von einer Vaterklasse und mehreren Interface-Klassen erben. Ein Interface ist wie eine Klasse, nur daß alle Methoden keinen Funktionskörper haben. Sie werden sozusagen nur abstrakt deklariert. Ein Interface dient also nur dazu anzugeben, daß eine Klasse gewisse Methoden implementiert. Wenn eine Klasse ein Interface erbt, sagt man, es implementiert das Interface. Ein Interface hat keine Eigenschaften (Variablen). Natürlich muß jede Klasse, die das Interface implementiert, dessen Methoden tatsächlich deklarieren. Als Beispiel schreiben wir eine Quicksort-Routine, die beliebige Objekte sortieren kann. Dazu muß diese Routine die Objekte vergleichen können. Die Objekte selber bringen die Methode zum Vergleich mit. Die Quicksort-Routine muß wissen, daß die Objekte diese Methode implementieren. Hier ist zunächst einmal die Definition des Interface. interface SortObject { public int compare (SortObject o); } Wie man sieht wird der Funktionskörper von compare durch ein Semikolon ; ersetzt. Man kann nun eine beliebige Klasse sortierbar machen, wenn man compare implementiert. Wir wollen double-Werte sortieren. Dazu schreiben wir eine neue Klasse SortDouble. class SortDouble implements SortObject { private double X; public SortDouble (double x) { X=x; } public double getValue () { return X; } public int compare (SortObject o) 10 Interfaces { 10 - 2 SortDouble s=(SortDouble)o; if (X<s.X) return -1; else if (X==s.X) return 0; else return 1; } } Diese Klasse kann einen double-Wert aufnehmen und implementiert die compare-Methode. Nun folgt das Hauptprogramm, das eine statische Funktion Sorter.sort() aufruft. public class Test { public static void main (String args[]) { int i,n=1000; SortDouble x[]=new SortDouble[n]; for (i=0; i<n; i++) x[i]=new SortDouble(Math.random()); Sorter.sort(x); for (i=1; i<n; i++) if (x[i].getValue()<x[i-1].getValue()) System.out.println("Fehler beim Sortieren"); } } Die Methode Sorter.sort weiß nun nichts von double-Werten, sondern benutzt nur compare aus dem Interface SortObject. class Sorter { static public void sort (SortObject v[]) { QuickSort(v,0,v.length-1); } static private void QuickSort (SortObject a[], int lo0, int hi0) { int lo=lo0; int hi=hi0; SortObject mid; if (hi0>lo0) { mid = a[(lo0+hi0)/2]; while(lo<=hi) { while((lo<hi0) && (a[lo].compare(mid)<0)) ++lo; while((hi>lo0) && (a[hi].compare(mid)>0)) --hi; if(lo<=hi) { swap(a,lo,hi); ++lo; --hi; } } if(lo0<hi) QuickSort(a,lo0,hi); if(lo<hi0) QuickSort(a,lo,hi0); } } static private void swap (SortObject a[], int i, int j) { SortObject T; T=a[i]; a[i]=a[j]; a[j]=T; } 10 Interfaces 10 - 3 } Wie man sieht, sind Interfaces für verschiedene Zwecke nützlich. Abstrakte Klassen Tatsächlich ist es auch möglich, Klassen abstrakt zu deklarieren. Dazu erhalten sie das Schlüsselwort abstract, ebenso wie alle Methoden, die nicht mit einem Funktionskörper ausgestattet sind. Ein Beispiel ist abstract class GraphicsObject { ... abstract void paint (Graphics g); ... } Diese Klasse enthält die abstrakte Methode paint und muß daher selber abstrakt sein. Erst die Kinder von GraphicsObject sollen die paint-Methode implementieren. Es kann dann von GraphicsObject selbst auch keine Instanz angelegt werden, nur von seinen Kindern. Dennoch kann man etwa einen Vektor mit Instanzen von Kindern von GraphicsObject füllen und einfach die paint-Methode von GraphicsObject verwenden. Die Alternative ist, alle Kinder ein Interface GraphicsObject implementieren zu lassen. Dies ist aber nicht so logisch und hat außerdem den Nachteil, daß Interfaces keine Variablen vererben können. Enumeration Eine andere Anwendung ist das Interface Enumeration, das verschiedene Klassen verwenden. Es stellt zwei einfache Methoden zur Verfügung, um durch alle gespeicherten Objekte in einer Schleife zu gehen. Diese Methoden sind hasMoreElements und nextElement. Die Klasse Properties (ein Kind von Hashtabe) liefert mit der Methode propertyNames() eine Enumeration aller definierten Schlüssel zurück. Man kann damit alls Schlüssel und Ihre Werte auflisten. Im folgenden Beispiel tun wir dies für die System-Properties. import java.util.*; public class Test { public static void main (String args[]) { Properties p=System.getProperties(); Enumeration e=p.propertyNames(); while (e.hasMoreElements()) { String s=(String)e.nextElement(); System.out.println(s+": "+(String)p.get(s)); } } } Die Ausgabe ist hier user.language: de 10 Interfaces 10 - 4 java.home: C:\JDK1.1.6\BIN\.. java.vendor.url.bug: http://java.sun.com/cgi-bin/bugreport.cgi awt.toolkit: sun.awt.windows.WToolkit file.encoding.pkg: sun.io java.version: 1.1.6 file.separator: \ line.separator: user.region: DE file.encoding: 8859_1 java.compiler: symcjit java.vendor: Sun Microsystems Inc. user.timezone: ECT user.name: Rene os.arch: x86 os.name: Windows 95 java.vendor.url: http://www.sun.com/ user.dir: D:\java\kurs java.class.path: .;C:\JDK1.1.6\BIN\..\classes;C:\JDK1.1.6\BIN\..\lib\classes.zip;C:\JDK1.1.6 \BIN\..\lib\classes.jar;C:\JDK1.1.6\BIN\..\lib\rt.jar;C:\JDK1.1.6\BIN\..\li b\i18n.jar java.class.version: 45.3 os.version: 4.0 path.separator: ; user.home: C:\JDK1.1.6\BIN\.. Z.B. gibt die Systemeigenschaft file.separator an, welches Zeichen zur Trennung von Filenamen und Verzeichnis benutzt wird. Bei UNIX ist dies /. Beispiel Wir wollen ein Interface für unsere StringList-Klasse entwickeln. Als Objekt, das die Enumeration implementiert, verwenden wir einfach die StringList selbst. Eine Methode elements() gibt die StringList als Enumeration zurück. class StringList implements Enumeration { ... private StringElement Next=null; ... public boolean hasMoreElements () { return Next!=null; } public Object nextElement () { if (Next==null) return null; String s=Next.getString(); Next=(StringElement)Next.getNext(); return s; } public Enumeration elements () { if (Next!=null) return null; // Enumeration aktiv! Next=First; return this; } } public class Test { public static void main (String args[]) 10 Interfaces { 10 - 5 StringList L=new StringList(); for (int i=0; i<10; i++) L.append("Number "+i); Enumeration e=L.elements(); while (e.hasMoreElements()) { System.out.println((String)e.nextElement()); } } } Der Nachteil dieser Konstruktion ist, daß elements() versagt, während die Enumeration noch nicht abgearbeitet ist. Wenn es sein kann, daß zwei Aufzählungen der Liste gleichzeitig benötigt werden, so muß man ein separates Objekt erzeugen. class StringList { ... public Enumeration elements () { return new StringListEnumeration(First); } } class StringListEnumeration implements Enumeration { StringElement Next; public StringListEnumeration (StringElement first) { Next=first; } public boolean hasMoreElements () { return Next!=null; } public Object nextElement () { if (Next==null) return null; String s=Next.getString(); Next=(StringElement)Next.getNext(); return s; } } Auch dieser Ansatz ist nicht ganz sicher, da sich ja die Liste ändern kann, während die Enumeration abgearbeitet wird. Hier hilft dann nur eine Kopie, z.B. auf einen Vector (der selber eine elements-Methode hat). Übungsaufgaben 1. Schreiben Sie StringList.elements() um, so daß es die Strings auf einen Vector kopiert und dessen elements-Methode benutzt. Lösungen. Aufgaben ohne Lösung 1. Testen Sie die elements()-Methode von Hashtable anhand der SystemProperties.