Interfaces

Inhalt

 

 

Interfaces

 

Wiederverwendbare Routinen

 

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 dass alle Methoden keinen Funktionskörper haben. Sie werden sozusagen nur abstrakt deklariert. Ein Interface dient also nur dazu anzugeben, dass eine Klasse gewisse Methoden implementiert. Wenn eine Klasse ein Interface erbt, sagt man, es implementiert das Interface. Ein Interface hat keine Eigenschaften (Variablen).

Ein Interface ist ein Versprechen, dass gewisse Methoden implementiert sind.

Natürlich muss jede Klasse, die das Interface implementiert, dessen Methoden auch tatsächlich definieren, sont meldet der Compiler einen Fehler.

Die Hauptanwendung der Interface-Methode werden wir erst im nächsten Kapitel kennen lernen.

Wiederverwendbare Routinen

Als Beispiel schreiben wir eine Quicksort-Routine, die beliebige Objekte sortieren kann. Wie haben Quicksort schon einmal kennen gelernt, aber damals war der sortierte Datentyp ein double. Nun hat man die Wahl:

Java verwendet die letzgenannte Lösung. Hier ist zunächst einmal die Definition des Interface, das alle zu sortierenden Objekt implementieren müssen.

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 als Beispiel wieder 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)
    {   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. Die Klasse Sorter enthält eine entsprechende Routine, die den Quicksort-Algorithmus implementiert. Diese Methode bekommt ein Array aus Instanzen von SortObject übergeben. Da SortDouble dieses Interface implementiert, geht auch ein Array aus SortDouble.

public class Test
{   public static void main (String args[])
    {   int i,n=1000;

        // Get an array of random numbers:
        SortDouble x[]=new SortDouble[n];
        for (i=0; i<n; i++) x[i]=new SortDouble(Math.random());

        // Sort it:
        Sorter.sort(x);

        // Test, if the order is correct:
        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;
    }
}

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 muss 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, dass Interfaces keine Variablen vererben können.

Enumeration

Eine andere Anwendung ist das Interface Enumeration, das verschiedene Container-Klassen verwenden, wie zum Beispiel die Klasse Vector. 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 alle 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));
        }
    }
}

Eine Enumeration kann also folgendermaßen durchlaufen werden.

Die Ausgabe bei diesem Programm auf einem unserer Rechner war

user.language: de
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\rene\kurs
java.class.path: .;C:\JDK1.1.6\BIN\..\classes ...
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 /.

Eine weitere Anwendung von Enumeration ist in der Klasse Vector implementiert. Enumeration funktioniert mit wie Vector im folgenden Code ersichtlich. Man muss nur beachten, dass die Objekte, die nextElement() zurück gibt, in den richtigen Typ zu konvertieren sind.

Vector V=new Vector();
for (int i=1; i<=10; i++) V.add(new Integer(i));
Enumeration e=V.elements();
while (e.hasMoreElements())
{   int i=((Integer)e.nextElement()).intValue();
    System.out.println(i);
}

Übungsaufgaben

Lösungen.

Aufgaben ohne Lösung

  1. Testen Sie die elements()-Methode von Hashtable anhand der System-Properties.

Zurück zum Java-Kurs