Thursday, October 28, 2010

Typ > Klasse. Warum Klassen auch in Java nur eine Untermenge der möglichen Typen sind

In diesem Beitrag geht es primär darum, den nebulösen Begriff des Typsystems etwas genauer zu definieren. Vor allem soll (in einem Follow-Up) aufgezeigt werden, dass das "objektorientierte Paradigma" auch nur ein Sonderfall der etwas generelleren und mächtigeren "strukturellen" Typsysteme ist. Und es soll gezeigt werden, warum Java seit 1.5 zwischen Klassen und Typen unterscheidet!

Am Anfang war alles ganz einfach. Vielleicht etwas zu einfach: Da Sprachen mit statischen Typsystemen im Vergleich zu den dynamischen fast zwangsläufig dazu tendieren, komplex zu sein, musste eine Vereinfachung her. Also hat man sich beim Design von Java wohl so ungefähr folgendes überlegt:

Diskussion zwischen James und dem Sonnengott
J: "Typen sind schon ein ganz schön komplexes Thema. Ich mag Typen. Aber ich will es einfach halten."
S: "Unser Business-Plan. Wir brauchen Ergebnisse! Lösungen!"
J: "Jaaa... Moment!"
S: <Uhrtick>
J: "Ok, wie wärs damit: Typ == Klasse! Die einzig zulässigen Typen sind Klassen!"
S: "Gut. Die Propaganda ist in schon in Arbeit. Typ == Klasse. Das wird das Fundament der ganzen OO werden!"
J: "Und wenn wir uns irren? Oh.. Und ich hab scheiße gebaut. Was ist mit den Primitiven? Und den Arrays?"
S: "Klappe!"

So oder so ähnlich muss es wohl ergegangen sein. Egal. Worum es mir heute geht ist nur folgendes:
Typen sind keine Klassen... aber Klassen sind Typen.

Fangen wir wie folgt an: Und zwar mit einer rudimentären Definition:

Typ = Einschränkung der möglichen Werte für einen Bezeichner (üblicherweise eine Variable oder ein Funktionsparameter). Typen stehen bei den statischen Typsystemen im Mittelpunkt, die versuchen durch eine Validierung der Typen möglichst viele Fehler von vornherein durch einen Compiler zu verhindern.

Klassentyp = Einschränkung der möglichen Werte für einen Bezeichner auf einen bestimmten Typ, der von der angebenen Klasse oder einer von ihr abgeleiteten Klassen sein muss. Offizielle Bezeichung: Nomineller Typ mit Subtypisierung.


Mögliche Typen in Java

Der Klassentyp ist nur ein Sonderfall des generischen "Typs". In Java war die Klasse in Java vor 1.5 der einzig zugelassene Typ - Arrays / Primitive sind Fehler im Sprachdesign, die ich hier ignoriere. Bis heute ist sie traditionell auch der am häufigsten anzutreffende Typ. Durch die Generics mag sich dies grundlegend geändert haben. Aber das interessante ist: Selbst in Java < 1.5 gibt es schon einen Unterschied!

Beispiel 1: Subklassenbildung

List list = new ArrayList();
Bezeichner: list;      Klassentyp: List;       Klasse: ArrayList;    Wert: []

Das Bezeichner mit einem Obertyp auf Objekte von einer Subklasse verweisen können, ist natürlich in der Objektorientierung ein alter Hut. Aber es ist eine sehr wichtige Erkenntis! Wichtig deshalb, weil durch das obige Konstrukt das Typsystem weniger weiß als es wissen müsste, da ich seine Wertebereich bewusst stärker eingeschränkt habe, als es nötig gewesen wäre. Ich hätte ja auch einfach das folgende schreiben können: ArrayList list = new ArrayList(). Dafür könnte ich dann auch keine LinkedListen mehr für die list-Variable verwenden.

Beispiel 2: Typparameter

List<String> list = new ArrayList<String>();
Bezeichner: list;      Klassentyp: List<String>;       Klasse: ArrayList;    Wert: []

Was dieses Beispiel so interessant macht, ist das hier am deutlichsten wird, das Generics in Java nur für das Typsystem interessant sind und das Typen mehr als nur Klassen sind. Ich erschaffe hier zur Laufzeit anhand der Typparameter einen neuen Typ - List<String>. Die Klasse hingegen hat sich dadurch nicht geändert, sie weiß nicht einmal etwas über den Typ. Im besten Fall könnte ich in einer Sprache ohne erasure noch wissen dass ich hier eine Klasse mit einem Typ String (nicht: Klasse String) instanziert habe.
In Sprachen wie Java < 1.5 ohne Typparameter, in denen Typen und Klassen immer dasselbe sind, lässt mich das Typsystem an der wichtigsten Stelle hängen: Reine Klassentypen sind nicht mächtig genug um damit alle wichtigen Typen abbilden zu können. Praktisch bedeutet dies, dass ich in diesen Sprachen sehr oft mit "up-casts" arbeiten muss und damit alle meine Datenstrukturen nicht typsicher sind.

Beispiel 3: Typparameter können mehr sein als nur Klassen

Java nennt sie Generics, aber dennoch ist die Bezeichnung Typparameter die eigentlich wichtige. Die meisten können sicher verstehen, das Typparameter wie der Name sagt, Typen, keine Klassen erwarten. Ich will es dennoch kurz erklären:

Map<String,Set<Integer>> map = new HashMap<String,Set<Integer>>();

Alles klar? Wenn ich nur Klassen benutzen dürfte, wäre die Bezeichnung illegal, da Set<Integer> ein Typ ist, die Klasse hingegen nur Set. Das wäre aber ziemlicher Dreck und wird deshalb nicht gemacht. Merke: Es heißen Typparameter, nicht Klassenparameter. Und: Sie sind der Hauptgrund dafür, dass Typen und Klassen sich seit Java 1.5 unterscheiden.

Beispiel 4: Typen können Klassen kombinieren

Bis hierhin mag noch alles lustig gewesen sein, doch jetzt kommt etwas dass die meisten wahrscheinlich noch nicht wissen: Typen können sich sogar aus mehreren Klassen zusammensetzen (Kombinierte Typen):

interface X{

  <A extends Set<?> & Comparable<Set>> void action(A a);

}

Diese Methode erwartet als Eingabeparameter eine Variable von einem Typ, der sich aus den Typen Set und Comparable zusammensetzt. Hier wird ein neuer Typ definiert, der sich aus vorhandenen zusammensetzt. Dies ist ähnlich den normalen Typparametern, die die das definieren von "Ad-hoc" Typen aus existierenden Typen ermöglichen. Aber hiermit ist eine noch größere Flexibilität gegeben: Der kombinierte Typ enthält alle Methoden des Comparable UND des Set interfaces, stellt also quasi eine neue Schnittstelle dar OHNE dass vorher eine explizite Vererbungsbeziehung definiert werden müsste.

An dieser Stelle soll nicht über Sinn und Zweck von aus Klassen zusammengesetzten Typen gesprochen werden - diese sind meist eher bescheiden. Viel interessanter ist aber, das Java hier ein Feature hat, das Typen mächtiger macht als Klassen. Wie so oft bei Java ist hier ein mächtiges Feature, flexible Typendefinition ohne direkte Vererbung, vorhanden, aber so verkrüppelt und schwach dass es meist nur begrenzten Sinn macht.

Für alle die sich fragen was ich meine: Java basiert seit 1.5 fast auf einem strukturellen Typsystem. Die hier genannten Veränderungen mit den zusammengesetzten Typen haben nichts mehr mit den bisher bekannten starren Typhierarchien der Objektorientierung zu tun. Aber: Java geht den entscheidenden Schritt nicht: Es bleibt ein starres System mit schlechter Erweiterbarkeit. Es unterstützt nicht die flexible Definition von Typen, die überhaupt nicht mehr auf Klassen basieren, sondern erlaubt es nur, vorhandene Klassen zu kombinieren.

Ich werde im nächsten Artikel genauer auf das Thema der Typsysteme eingehen. Im Mittelpunkt wird dabei der Vergleich der statischen nominellen Typsysteme (C), der nominellen Typsysteme mit Subtypen (Java, C++) und der strukturellen Typsysteme (Haskell, Scala) stehen.

No comments:

Post a Comment