Inhalt |
|
|
|
|
|
|
|
|
|
|
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 tatsächlich definiert. 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 Interfaces, nämlich die Bearbeitung von Ereignissen,-werden wir erst im nächsten Kapitel kennen lernen.
Im folgenden Beispiel verwenden wir ein Interface für Objekte, die einen double-Wert haben sollen. Im Hauptprogramm werden diese Objekte in einem Array vom Typ Value gespeichert.
Bei der Definition des Interface muss man lediglich alle Funktionen, die man verwenden will abstrakt gestalten. Der Funktionskörper fehlt. Diese Funktionen werden in jeder Klasse, die das Interface implementiert, definiert.
/** * Value Interface. Has a getValue() method. */ interface Value { public double getValue (); } /** * Asset Value */ class Asset implements Value { String name; double value; public Asset (String name, double value) { this.name = name; this.value = value; } @Override public double getValue () { return value; } } /** * Account Value */ class Account implements Value { String name; int number; double value; public Account (String name, int number, double value) { this.name = name; this.number = number; this.value = value; } @Override public double getValue () { return value; } } public class InterfaceTest { public static void main (String[] args) { // create two values Value[] values = new Value[2]; values[0] = new Asset("IBM", 21500.0); values[1] = new Account("DB", 67585955, 20000.0); // sum up all values double sum = 0.0; for (Value v : values) sum += v.getValue(); System.out.println("Sum of values : " + sum); } }
Als Beispiel schreiben wir eine Quicksort-Routine, die beliebige Objekte sortieren kann. Gleich zu Beginn sei gesagt, dass Java eine solche Routine und auch das zugehörige Interface schon besitzt. Es ist aber lehrreich, die Fuktionalität nachzuprogramieren.
Wie haben Quicksort schon einmal kennen gelernt, aber damals war der sortierte Datentyp 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. Das Interface ist eine abstrakte Klasse.
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. DieLogik ist die gleiche, die wir dür double verwendet haben.
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; } }
Wie gesagt, enthält Java schon Routinen zum Sortieren. Die bekannteste ist Arrays.sort(v), die für beliebige double-Arrays oder andere Arrays verwendet werden kann. Im folgenden Programm verwenden wir Collections.sort(), was auch Vektoren sortieren kann. Der Vektor muss aber aus Objekten bestehen, die das Interface Comparable implementieren.
Damit wir hier beim Vergleich eine Typumwandlung vermeiden, verwenden wir den Typ von Comparable, der mit Objekten vom Typ Name vergleichen kann. Dadurch kann man die Funktion compareTo einfacher gestalten.
Es ist auch interessant den Konstruktor von Name anzusehen. Die Parameter haben denselben Namen wie die Eigenschaften. Damit das geht, müssen wir die Argumente mit this zuweisen.
import java.util.Collections; import java.util.Vector;
/** * Klasse, die einen Namen mit Vorname und Nachname aufnimmt. * Zum Sortieren wir das Interface Comparable<Name> implementiert. */ class Name implements Comparable<Name> { public String vorname, nachname; public Name(String vorname, String nachname) { this.vorname = vorname; this.nachname = nachname; } @Override public int compareTo (Name o) { // Wenn die Nachnamen gleich sind, muss mit den Vornamen // verglichen. if (nachname.equals(o.nachname)) return vorname.compareTo(o.vorname); return nachname.compareTo(o.nachname); } @Override public String toString () { return nachname + ", " + vorname; } } public class Test { public static void main (String args[]) { Vector<Name> names = new Vector<Name>(); names.add(new Name("Schmidt", "Beate")); names.add(new Name("Schmidt", "Anton")); names.add(new Name("Schmidt", "Xaver")); names.add(new Name("Müller", "Ute")); names.add(new Name("Träger", "Wilhelm")); Collections.sort(names); for (Name name : names) System.out.println(name); } }
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.
Programmieren Sie eine Klasse, die ein Array von int aufnehmen, und solche Arrays alphabetisch miteinander vergleichen kann. Erzeugen Sie zufällig lange Instanzen dieser Klasse mit zufälligen Inhalten.