Einführung und Theorie
Übersicht: Einführung und Theorie
Problem der Containerklassen
Häufig kommt es bei der Programmierung vor, dass Container von Objekten verarbeitet werden müssen. In Java hat man dann zur Zeit nur zwei Möglichkeiten:
- man verwendet die Collection-Bibliothek von Java
- man schreibt spezielle Containerklassen
Ein Codebeispiel mit Bibliotheksklassen java.util.List und java.util.ArrayList:
01 import java.util.*;
02
03 class Bsp01 {
04 public static void main(String[] args){
05 String str = "hallo";
06 List container = new ArrayList();
07 container.add(str);
08 String str2 = (String) container.get(0);
09 }
10 }
|
Bsp/Bsp01.txt |
Codebeispiel 1
Ein Codebeispiel mit eigener StringList
01 public class Bsp02 {
02 public static void main(String[] args){
03 String str = "hallo";
04 StringList container = new StringList();
05 container.add(str);
06 String str2 = container.get(0);
07 }
08 }
09
10 class StringList {
11 String[] content = new String[10];
12 int fillIndex = 0;
13 public void add(String s){ content[fillIndex++] = s; }
14 public String get(int index){ return content[index]; }
15 }
|
Bsp/Bsp02.txt |
Codebeispiel 2
Beide Ansätze haben Vor- und Nachteile. Die Vorteile der ersten Lösung sind natürlich die Codewiederverwendung. Das Rad wird nicht neu erfunden und man kann davon ausgehen, dass die Bibliotheken ausgereifter sind als eigene Entwicklungen. Jeder Java-Entwickler kennt diese Klassen und kann intuitiv damit umgehen. Der Code kann leichter ausgetauscht werden. Nachteile liegen in der Art, wie die Objekte abgespeichert werden, genauer: mit welchem Typ, nämlich "Object". In Java ist dies die einzige Möglichkeit allgemein gültige Container zu schaffen. Die abstrakte "Object" Klasse steht in der Vererbungshierarchie an oberster Stelle. Alle Klassen sind implizit von Object abgeleitet und können daher auch einer Variablen vom Typ Object zugewiesen werden. Dies ist ein Up-Cast in der Klassenhierarchie, der harmlos ist, weil er immer funktionieren wird. Problematisch wird es, wenn man die speziellen Typen zurück benötigt, die in dem Container gespeichert wurden. In diesem Fall bleibt nichts anderes übrig als dem Compiler den Typ vorzugeben, also einen so genannten Down-Cast durchzuführen. Dieser wird zur Laufzeit von der Java Virtual Machine (JVM) überprüft. Die JVM vergleicht den Typ der aus dem Container entnommen wird mit dem Typ in den "gecastet" wird und wirft bei einem ungültigem Downcast eine ClassCastException. Eine Prüfung zur Übersetzungszeit ist hierbei nicht möglich. Der Programmierer muss sich merken, welchen Typ er im Container gespeichert hat.
Der zweite Ansatz, spezielle Containerklassen zu schreiben, ist in der Regel nicht praktikabel. Den Vorteilen, Typsicherheit durch vollständig Typprüfung zur Compilezeit und damit höhere Geschwindigkeit durch überflüssige Casts, steht der große Nachteil gegenüber, dass man für jeden speziellen Typ einen eigenen Container oder zumindest eine Wrapper-Klasse schreiben muss. Das führt leicht zu einer "copy und paste Programmierung" die auf Dauer zu unwartbarem Code führt.
Aus diesem Grund verwendet man als Javaprogrammierer üblicherweise die erste Lösung und findet sich mit den vielen Casts im Programmtext ab. Was Java fehlt, ist das Konzept der Generizität, auch Typparametrisierung oder parametrischer Polymorphismus genannt. Dies ist eine zusätzliche Abstraktionsebene im Softwareentwurf.
Definition von Generizität
Eine Definition von Generizität
"Eine generische Klasse (parameterized class, template) beschreibt eine Familie von Klassen mit einem oder mehreren formalen Parametern. Parameter einer generischen Klasse sind Typ-Parameter oder Konstanten-Parameter. Ein Typparameter ist ein Bezeichner, der innerhalb der Klasse wie ein gewöhnlicher Typ verwendet werden kann."
Diese Definition ist etwas eingeschränkt da sie nur das parametrisieren von Klassen beschreibt und nicht typparametrisierte Methoden oder beschränkte Generizität. Auch die Tatsache, dass sich ein Typparameter wie ein gewöhnlicher Typ verhält, ist zumindest in der Java Implementierung nicht vollständig gegeben.
Eine allgemeine Definition der Rolle von Generizität gibt Bertrand Meyer [
Meyer98] (S. 97):
"Genericity is a facility for the authors of supplier modules. It makes it possible to write the same supplier text when using the same implementation of a certain concept, applied to different kinds of object."
Theorie von Generizität
Bertrand Meyer [
Meyer98] definiert eine horizontale Variation von Klassen (Abstraktion und Spezialisierung, zwei Richtungen von Vererbung) und eine vertikale (die Generizität). Als Beispiel für Vererbung zeigt Meyer die Struktur SET[Book], LIST[Book] und LINKED_LIST[Book] wobei LIST eine Spezialisierung von SET ist und LINKED_LIST eine Spezialisierung von LIST. Mit Typarametern kann man jetzt in jeder Ebene den Typ Book durch einen anderen (Person, Journal, ...) ersetzten. Die Typparameter können wiederum in einer Vererbungsbeziehung stehen. Dies wird in späteren Java Beispielen gezeigt.
Zwei Qualitätsziele von Software werden durch Generizität leichter erreicht:
- Reliability (Zuverlässigkeit, d.h. Typsicherheit durch explizite Typangaben)
- Reusability (Wiederverwendbarkeit, ein Softwareelement beschreibt verschiedene Varianten)
Generizität ist unabhängig von Vererbung und Objektorientierung. Auch reine prozeduale oder funktionale Sprachen können über Generizität verfügen. Haskell [
Thompson99] ist ein gutes Beispiel für eine funktionale Sprache, in der die Generizität ("polymorphe Datentypen") sehr geschickt umgesetzt wurde.
Durch die Verbindung von Vererbung und Generizität lassen sich vielseitigere Softwarekomponenten erschaffen, als das mit nur einer der Techniken möglich wäre. Unter anderem lässt sich elegant eine so genannte "constrained genericity" umsetzen. Als deutsche Übersetzung wird dafür im folgenden beschränkte Generizität verwendet. Hierbei schränkt man den Typ auf bestimmte Eigenschaften ein. Beispielsweise sollen in einer Klasse beliebige Typen genutzt werden, die aber eine oder mehrere Funktionen haben müssen. Eine Vergleichsfunktion für allgemeine Sortierfunktionalität wäre ein denkbares Beispiel. Bei einer Hash-Funktion würde das gleiche Problem auftreten, aber das wurde in Java bereits dadurch "gelöst", dass die Klasse Object eine Hash-Funktion implementiert hat. Haskell verfügt auch über das Konzept von beschränkten Typen, dass dort mit "Typklassen" umgesetzt wurde.
Damit es keine Verwechslung gibt ein Beispiel:
Es sei eine Klasse Vector gegeben. Diese sei so definiert, dass sie einen Typen als Parameter bekommt, der von einer bestimmten Klasse abgeleitet sein muss: class Vector[G -> Numeric]
Dies ist die Syntax die Meyer verwendet (nicht die für Java!). Sie sagt aus, dass in der Klasse ein Typ G verwendet werden kann (z.B. für alle manipulierenden Funktionen) der von Numeric abgeleitet ist. Die Klasse kann jetzt mit generischen Parametern wie z.B. Real oder Complex versehen werden, wenn diese von Numeric abgeleitet sind. Innerhalb der Klasse können auf einem Objekt des generischen Typs alle Numeric Funktionen genutzt werden. Was ist der Vorteil gegenüber einer Klasse, die man gleich als NumericVector implementiert, wo also statt G gleich der Typ Numeric eingetragen wird? Typsicherheit! Bei der NumericVector Klasse ergibt sich wieder das Problem, dass diese Klasse auch Objekte vom Typ Real speichern kann, der Programmierer aber bei allen "get-Funktionen" einen Downcast machen muss. Im Gegensatz dazu erlaubt die Klasse Vector[G -> Numeric] eine Typerzeugung wie z.B. Vector[Real], bei der der Compiler alle angewendeten Funktionen auf diesen Typen überprüfen kann. Wenn die Klasse Vector in diesem Beispiel auch noch von Numeric abgeleitet wird, können auch rekursive Strukturen wie Vector[Vector[Integer]] typsicher beschrieben werden.
Bertrand Meyer beweist in seinem Buch auch, dass Vererbung mächtiger ist als Generizität (Anhang B). Es ist möglich, Generizität in beschränkter und unbeschränkter Form durch Vererbung zu simulieren. Dies geht aber nur durch Codeverdopplung, Wrapperklassen und komplizierten Code mit vielen Typabfragen.
Code generated with AusarbeitungGenerator Version 1.1, weblink