Neuerungen in Java 5.0

Werbung
Neuerungen in Java 5.0
Stefan Bitzer ([email protected])
Sandra Lorenz ([email protected])
Alexander Weisser ([email protected])
Berufsakademie Stuttgart
Außenstelle Horb
Florianstraße 15
72160 Horb a.N.
Studium IT
Seminararbeit
Software Engineering
Betreuer: Prof. Dr. Olaf Herden
November 2005
Abstract: Sun hat sich mit der Entwicklung von Java 5.0 (auch Java 1.5.0) das
Ziel gesetzt, die Entwicklung mit Java zu vereinfachen und dabei die
Abwärtskompatibilität zu gewährleisten. Um mit Java 1.x kompatibel zu bleiben
wurde in den Java-Compiler eine Art Präprozessor integriert, der die neuen
Sprachkonstrukte aus Java 5.0 erkennt und in herkömmlichen Java-Code übersetzt.
Die vorliegende Dokumentation beschäftigt sich mit den Spracherweiterungen, die
Java 5.0, auch Tiger-Release genannt, mit sich gebracht hat. Seit Ende September
2004 steht die endgültige Version zum Download bereit.
Es werden dabei die neuen Sprachkonstrukte vorgestellt und mit Beispielen
verdeutlicht. Aber auch die Vor- und Nachteile der neuen Möglichkeiten werden
aufgezeigt. Für das Verständnis dieser Ausarbeitung sind einigermaßen gute
Kenntnisse von Java bis Version 1.4 nötig.
-2-
Inhaltsverzeichnis
1
2
Generische Programmierung..................................................................................... 4
1.1
Generische Datentypen....................................................................................... 5
1.2
Klassenschablonen.............................................................................................. 8
1.3
Methodenschablonen ........................................................................................ 10
1.4
Typeeinschränkung........................................................................................... 11
1.5
Wildcards.......................................................................................................... 13
1.6
Änderungen an der Collection API................................................................... 16
1.7
Mögliche Fehlerquellen beim Umgang mit Generics ....................................... 18
Sonstige Erneuerungen ........................................................................................... 19
2.1
Vereinfachte Syntax für die Iteration................................................................ 19
2.2
Autoboxing / Autounboxing ............................................................................. 21
2.3
Syntaktische Unterstützung für Aufzählungswerte (Enumerations)................. 22
2.4
Statischer Import............................................................................................... 24
2.5
Erweitertes Look and Feel ................................................................................ 26
2.6
Annotations – Metadaten im Quellcode ........................................................... 27
2.7
Variable Parameterübergabe............................................................................. 29
3
Zusammenfassung................................................................................................... 30
4
Literaturverzeichnis ................................................................................................ 31
-3-
1
Generische Programmierung
Eine der wichtigsten Neuerungen in Java 5.0 ist die Einführung der so genannten
Generics. Sie bringen eine deutliche Veränderung mit sich, die sich vor allem in einem
kleineren Codieraufwand niederschlagen, was später in diesem Abschnitt aufgezeigt
wird. Aus Sicht der Programmierer war dieser Schritt schon lange überfällig. Da Java
jedoch ursprünglich den Anspruch hatte, möglichst unkomplizierte Programmierung zu
ermöglichen, wurde bei der Konzeption von Java dieser Aspekt bewusst ausgelassen.
In der Sprache C++ ist eine Programmierung mit generischen Datentypen schon von
Anfang an implementiert und unter den so genannten Templates bekannt. Die Templates
stellen jedoch eine komplett andere Realisierung dar und können eigentlich nicht direkt
mit den Generics in Java verglichen werden.
Gleichzeitig werden die Generics in den Klassen der Collection API konsequent
umgesetzt und sollten deshalb in neuen Programmen auch eingesetzt werden. Collection
API ist der Name für die bei Java mitgelieferten Klassen, in denen viele Funktionen
schon zur Verfügung stehen und nicht extra programmiert werden müssen. Durch den
Einsatz der in Java 5.0 neuen Möglichkeiten, die unter dem Namen Generics
zusammengefasst sind, ist eine Typ-Sicherheit im Umgang mit Objekten fast garantiert,
denn eventuell falsche Typzuweisungen werden schon zur Compile-Zeit erkannt. Ganz
ohne Hindernisse können die Generics jedoch auch nicht eingesetzt werden, denn es gibt
immer wieder Stolperfallen, die beachtet werden müssen. Diese Einführung zeigt solche
Problemstellen auf.
Der vom neuen Compiler erstellte Bytecode ist abwärtskompatibel, denn er erzeugt den
gleichen Code wie Java 1.4. Dies wird im Prinzip so realisiert, dass die neuen
Sprachkonstrukte von einer Art Präprozessor erkannt und dann die entsprechenden
Konstrukte erstellt werden. Dabei ist zu beachten, dass ein Java-Programm durch diese
neuen Konstrukte keinesfalls schneller wird, sondern nur übersichtlicher im Quellcode
erscheint. Im Gegenteil: Durch die einfachen Konstrukte wird der Programmierer sogar
dazu verleitet, schlechten Code zu schreiben. Es ist nicht mehr sofort erkennbar, dass an
den jeweiligen Stellen teilweise unnötige und zeitaufwendige Typecasts zur Laufzeit
durchgeführt werden müssen!
Die allgemeine Syntax der Generics erfordert an verschiedenen Stellen nach dem
Klassennamen die Nachstellung des durch spitze Winkelklammern eingeschlossenen
Typnamen. So wird eine Liste, die ausschließlich Objekte des Typs Integer enthält als
List<Integer> = new List<Integer>();
definiert.
Wie in Java durch die Objektorientierung üblich, sind diese neuen Festlegungen nur mit
Objekten möglich. Primitivtypen können nicht verwendet werden.
-4-
Im Folgenden werden nun die Vorteile der neuen Sprachkonstrukte aufgezeigt. Aber
auch entstandene Nachteile und Probleme bei der Verwendung bzw. in der
Laufzeitoptimierung sollen nicht unaufgezeigt bleiben. Es wird dabei auf die an
verschiedenen Stellen hinzugefügten Möglichkeiten eingegangen. Dies betrifft den
Bereich generische Datentypen, die Möglichkeit Klassen- bzw. Methodenschablonen
anzulegen genauso, wie das Verwenden von Typeinschränkungen und Wildcards und die
Änderungen an der Collection API.
1.1 Generische Datentypen
Warum sollen nun generische Datentypen eingesetzt werden? Bisher ging es doch auch
ohne! Nun, in Java kann man schon von Anfang an durch die gegebene
Vererbungshierarchie sämtliche allgemein benötigten Funktionen für die Klasse Objekt
programmieren, von der alle (nicht-primitiven) Datentypen erben. Dadurch muss eine
Funktion wie zum Beispiel das Sortieren oder auch Collections wie Listen nur einmal
programmiert werden und können für jeden Typ von Daten verwendet werden. Diese
Eigenschaft wird auch Polymorphie genannt.
Doch genau hier treten einige Probleme auf. Die Konzeption von Java sieht im Prinzip
vor, dass sämtliche Typen zur Compile-Zeit überprüft werden. Ein Aufruf
Auto meinAuto = new Auto();
Haus meinHaus = meinAuto;
wobei Auto nicht von Haus erbt, gibt einen Fehler in der 2. Zeile beim Compilieren, der
zum Abbruch führt. Dies ist auch vollkommen richtig so.
Die Überprüfung wurde jedoch bisher nicht überall konsequent in Java umgesetzt. Es
können beispielsweise beliebige Typen zur Laufzeit in die Collections eingefügt werden.
Es ist also ohne weiteres bis Version 1.4 erlaubt, zunächst String-Objekte, dann IntegerObjekte, dann irgendwelche eigenen Objekte und so weiter in eine Liste einzufügen. Da
es alles Objekte sind, die eingefügt werden, meldet hier weder der Compiler einen
Fehler, noch gibt es zur Laufzeit des Programms Probleme.
Wenn die Objekte jedoch später im Programmablauf wieder aus der Liste geholt werden,
müssen sie auf den entsprechend gewünschten Typ mit einem Cast zurückgeführt
werden. Wird hier nun ein anderes Objekt zurückgegeben als erwartet, gibt es zur
Laufzeit einen Fehler, der vom Compiler nicht erkannt wurde:
Stack meinStack = new Stack();
meinStack.push("hallo");
meinStack.push(Integer(2));
//korrekt
//auch korrekt
String rueckgabe = (String) meinStack.pop();
-5-
//Fehler!!
An diesem Punkt setzen die generischen Datentypen an. Das Ziel ist, dass solche Fehler
bereits beim Compilieren erkannt werden und so der Testaufwand der Programme
deutlich verkleinert werden kann. Es müssen dann nicht mehr sämtliche möglichen
Programmpfade aufgefunden und durchlaufen werden. Dies wird so realisiert, dass
beispielsweise dem verwendeten Collection-Objekt der Typ mit angegeben wird, der
darin gespeichert werden soll. Dabei werden die schon erwähnten spitzen
Winkelklammern verwendet:
List meineListe<String> = new List<String>();
Mit dieser Angabe überprüft der Compiler beim Übersetzen des Programms bei jeder
Einfügeoperation, ob ein gültiges Objekt übergeben worden ist. Wenn nicht, bricht er
mit einem Fehler ab. Gleichzeitig spart sich der Programmierer beim Herausnehmen
eines Objektes den expliziten Typecast zu programmieren – er wird automatisch
gemacht. So ist zu jeder Zeit eine korrekte Behandlung der Objekte ohne unschöne
Typecast-Errors zur Laufzeit garantiert:
List meineListe<String> = new List<String>();
meineListe.add("hallo");
meineListe.add(Integer(2));
//korrekt
//Fehler beim Compilieren
Mit dieser Compiler-Erweiterung wurden natürlich auch neue Fehlermeldungen nötig.
So gibt der Compiler nun beim Auffinden eines dem Beispiel entsprechenden Fehlers
einen Fehler über die inkompatiblen Typen im Quelltext aus.
Um mit vorherigen Versionen von Java abwärtskompatibel zu bleiben, sprich dass alter
Quellcode weiterhin compiliert werden kann, ist die Angabe des Typs in den spitzen
Winkelklammern nicht zwingend notwendig. Werden sie weggelassen unterlässt der
Compiler die Type-Überprüfung, gibt aber eine entsprechende Warnung aus, die den
Programmierer darauf hinweisen, dass hier prinzipiell Probleme auftreten können. Dies
funktioniert in beiden Richtungen. Es kann also eine herkömmlich programmierte Klasse
ohne Probleme in einem Programm mit Generics instanziiert werden und auch
umgekehrt. Folgender Code ist also gültig:
-6-
public class Test
{
public static void funktion1(List a)
{
…
}
public static void funktion2(List<Integer> a)
{
…
}
public static void main(String[] args)
{
List<Integer> auflistung = new ArrayList<Integer>();
funktion1(auflistung);
ArrayList auflistung2 = new ArrayList();
funktion2(auflistung2);
}
}
Es wird aber empfohlen, bei Gelegenheit alle Klassen auf Generics umzustellen, da alle
vom Compiler ausgegebenen Warnungen eine potentielle Risikostelle darstellen und
deshalb als richtige Fehler verstanden werden sollten. An diesen Stellen können
weiterhin Typcast-Errors zur Laufzeit entstehen!
Durch die Abwärtskompatibilität entstehen aber auch wieder Probleme, die bei der
Programmierung beachtet werden müssen und sonst wieder zu den alten Problemen
führen. Folgender Code wird mit Generics für stack1 korrekt kompiliert, verursacht aber
einen Laufzeitfehler, da stack2 beliebige Objekte erlaubt:
Stack<Integer> stack1 = new Stack<Integer>();
Stack stack2 = stack1;
// stack2 referenziert auf stack1
stack2.push(Color.RED);
// erlaubt - stack2 ist allgemein
Integer neu = stack1.pop(); // Fehler!
Es ist also weiterhin wichtig, dass der Programmierer beim Erstellen des Quellcodes
sorgfältig vorgeht, denn sonst entstehen schnell wieder die alten Probleme. Diese können
nur dadurch entstehen, dass es auf der Bytecode-Ebene keine Generics gibt. Sie werden
nur zur Compile-Zeit beachtet und dann entfernt.
Aus diesem Grund kann der Typ zur Laufzeit auch nicht auf den Generics-Typ abgefragt
werden. Der Aufruf
System.out.println(stack1.getClass() == stack2.getClass());
-7-
liefert nicht wie vielleicht erwartet false, sondern true. Der Grund ist, dass beide Objekte
ganz normale Instanzen von Stack sind, die im Prinzip mit Objekten arbeiten. Nur der
Compiler kennt den Unterschied und macht die Typüberprüfungen. So kann auch nicht
mit
stack1 instance of Stack<Integer>
//geht nicht
überprüft werden, ob es sich um einen Stack handelt, der nur Integer-Werte akzeptiert.
1.2 Klassenschablonen
Wie schon eingangs erwähnt, sind die Generics in Java an die Templates in C++
angelehnt, jedoch an einigen Stellen deutlich verbessert worden. Das Ziel, das hinter
dieser Idee steckt, ist das Einsparen von Programmieraufwand und somit die Steigerung
der Übersichtlichkeit durch weniger Quellcodezeilen.
Es soll ermöglicht werden, dass eine Klasse bzw. Methode nur einmalig für viele
verschiedene Objekt-Typen geschrieben werden muss. In Java ist dies schon seit Anfang
an möglich. Realisiert wurde es dadurch, dass sämtliche Objekt-Typen von Object
erben. Eine Methode, die nun allgemein verwendet werden soll, muss nun nur den Typ
Object erwarten und ermöglicht so die Übergabe verschiedenster Typen.
Dies bedeutet aber auch, dass nicht sinnvolle Parameter übergeben werden können. Hier
setzen die Generics an. Eine Klasse kann so definiert werden, dass dem davon
instanziierten Objekt über die Generics-Notation der explizite Typ, der zum Einsatz
kommen soll, übergeben wird:
-8-
public class Test<T>
{
private T Wert;
public Test(T Wert) {}
public setValue(T Wert)
{
this.Wert = Wert;
}
public T getValue()
{
return Wert;
}
}
public class Haupt
{
public static void main(String[] args)
{
Test versuch<String> = new Test<String>();
versuch.setValue("hallo");
System.out.println(versuch.getValue);
}
}
Die in den spitzen Winkelklammern übergebene Variable kann einen beliebigen Namen
(außer den reservierten Symbolen) haben, sollte aber aus Gründen der Übersicht nur aus
einzelnen Grossbuchstaben bestehen. Der übergebenen Variable wird beim Erstellen
eines Objektes von der Klasse der zu verwendende Typ mit angegeben. Er kann dann
innerhalb der Klasse überall verwendet werden.
Es kann nun notwendig sein, Klassen mit bestimmten Abhängigkeiten von
unterschiedlichen Typen zu erstellen. Um hier die Typsicherheit innerhalb der Klasse zu
garantieren, ist es sinnvoll der Klasse mehr als nur einen generischen Typ zu übergeben:
public class Test<T, V>
{
private T Wert1;
private V Wert2;
…
}
Im Gegensatz zu C++ wird jedoch nicht für jeden im Programm implementierten Typ
eigener Code erzeugt, sondern immer der gleiche verwendet und nur der Typ zur
Compile-Zeit geprüft. Dadurch wird gegenüber C++, was dort auch als Code Bloat
bezeichnet wird, unnötiger Code gespart.
Durch dieses in Java 5.0 neue Vorgehen ist die Typsicherheit trotz der hohen Flexibilität
in der Verwendung der Klasse gewährleistet.
-9-
1.3 Methodenschablonen
Wie bei den parametrisierten Klassen können auch Methoden mit den neuen
Möglichkeiten der Generics erstellt werden:
static <T> int suche(T[] quelle, T suchobjekt)
{
for(int i = 0; i < quelle.length; i++)
if(suchobjekt.equals(quelle[i])) return i;
}
return -1;
}
Dabei muss beim Aufruf der generischen Methode der gewünschte Typ nicht mit
übergeben werden. Er wird automatisch erkannt. Die Funktion kann also einfach
folgendermaßen aufgerufen werden:
Array meineListe<String> = new Array<String>();
// Array hier auffüllen
if(suche(meineListe, "hallo") != -1)
System.out.println("gefunden!");
Der Compiler überprüft die übergebenen Parameter. So sind Aufrufe wie z.B.
System.out.println(suche(meineListe, Integer(3)));
ungültig und erzeugen einen Fehler schon beim Compilieren.
- 10 -
1.4 Typeinschränkung
Durch die Schablonen ist es nun möglich, Methoden bzw. Klassen für unterschiedliche
Typen zu verwenden um Code einzusparen. Bei bestimmten Anwendungen aber führt
dies zu einem Laufzeitfehler, wenn die übergebenen Objekte nicht von dem erwünschten
Typ sind, beziehungsweise dieses Objekt nicht die geforderte Funktionalität beinhaltet.
So ist es zum Beispiel möglich, dass es trotz der Verwendung von Generics zu Fehlern
kommen kann, wenn Objekte bestimmte Eigenschaften nicht besitzen, die aber innerhalb
einer Klasse oder Methode verwendet werden.
Um den Typ der Objekte, die übergeben werden, von vorne herein bereits
einzuschränken und somit Fehler zu vermeiden, kann mit den Generics eine
Typeinschränkung vorgenommen werden. Diese Typeinschränkung erfolgt mit dem in
Java eingesetzten Schlüsselwort extends. So ist es zum Beispiel möglich, nur Objekte
innerhalb einer Methode zu erlauben, die vom Type String bzw. Stringbuffer sind:
public static <T extends Charsequence> T (T val1, T val2)
{
…
}
In dieser Methode sind der Rückgabewert sowie die beiden Übergabewerte (im Typ)
durch das Schlüsselwort extends Charsequence eingeschränkt. Es kann aber auch
sehr gut möglich sein, das nicht alle Objekte diese Einschränkung bzw. unterschiedliche
Einschränkungen erhalten sollen. Um das zu ermöglichen, müssen mehrere generische
Typen übergeben werden, wobei jedes Objekt eine eigene Einschränkung erhalten kann:
public static <T extends Charsequence, V extends Comparable> T
(T val1, V val2)
{
…
}
Das Schlüsselwort extends zwingt somit dazu, dass nur Objekte verwendet werden
können, die bestimmte Schnittstellen bzw. Eigenschaften besitzen. Dies bringt den
Vorteil, dass bereits zur Compile-Zeit überprüft werden kann, ob alle Objekte, die diese
Methode verwenden, auch von ihren Eigenschaften her dazu berechtigt sind.
Viel interessanter ist es aber noch, dass auch Schnittstellen mit Generics erstellt werden
können und es erlaubt ist, diese zur Typeinschränkung zu verwenden. Die Definition
beim Interface sieht dann so aus:
interface IStack<T>
{
public void push(T object);
public T pop();
}
- 11 -
Um das Interface einsetzen zu können, müssen bei der Definition der Klasse ebenfalls
Generics verwendet werden, um den Typ an das Interface zu übergeben.
public class Stack<T> implements IStack<T>
{
public IntegerStack()
{
top = new StackStorage();
}
public void push(Integer obj)
{
StackStorage store = new StackStorage();
store.prev = top;
top = store;
store.object = obj;
}
public Integer pop()
{
Integer ret = top.object;
top = top.prev;
return ret;
}
}
Hier müssen nun jeweils die Methoden push und pop des Interfaces implementiert
werden. Aber genauso wie es bei Methoden möglich ist, die Typen über das
Schlüsselwort extends einzuschränken, ist es auch möglich dies bei den Interfaces zu
verwenden. Der Aufruf sieht folgendermaßen aus:
public class Stack<T> implements IStack<U extends T>
{
…
}
- 12 -
1.5 Wildcards
Bisher waren ausschließlich Instanziierungen von parametrisierten Typen möglich, bei
denen der Typparameter durch einen konkreten Typ wie Integer ersetzt waren. Das heißt,
das wenn man eine Liste einsetzt, die anhand Generics erzeugt wurde, es nicht möglich
ist diese Liste mit artverwandten Objekten des instanziierten Typs zu füllen, was auch
gezielt von den Javaentwicklern so gewollt ist. Ein Beispiel ist List<Integer>:
LinkedList<Integer> list = new LinkedList<Integer>();
list.add(new Integer(0));
Integer i = list.get(0);
Hier ist es nicht möglich, die Liste mit anderen Objekten als Interger Objekten zu füllen.
Artverwandte Objekte währen hier Objekte wie Long, Double, also kurz gesagt, alle
abgeleiteten Objekte vom Typ Number.
Durch diese Einschränkung war zwar der Typ gesichert, aber es gibt auch Situationen, in
denen nicht nur ein Typ zulässig ist, sondern auch Typfamilien beziehungsweise
Objekte, die sich in bestimmten Eigenschaften nicht unterscheiden. Um dies mit den
Generics verwirklichen zu können, müssen nicht konkrete Typen für den Typparameter
eingesetzt werden, sondern so genannte Wildcards. Es gibt 3 Arten von Wildcards:
<? extends [Type]>
<? super [Type]>
<?>
Wildcard-Instanziierungen bestehen aus einem Fragezeichen, das den unbekannten Typ
darstellen soll und so gesehen als Platzhalter für diesen unbekannten Typ fungiert. Diese
Wildcard-Instanziierungen gibt es ungebunden sowie gebunden. Sind die generischen
Typen gebunden, ist nur eine bestimmte Menge an Objekten zulässig. Sie sehen dann
zum Beispiel folgendermaßen aus:
List<? extends Number>
List<? super Long>
List<?>
Ein solches Wildcard bezeichnet keinen konkreten Typ, sondern eine Familie von
Typen. Das Wildcard
<? extends Number>
bezeichnet beispielsweise die Menge aller Typen, die vom Typ Number direkt oder
indirekt abgeleitet sind, d.h. die Familie aller Subtypen von Number, also Long,
Integer, usw., inklusive Number selbst und zählt zu der Familie der gebunden
Wildcards. Die Wildcard-Instanziierung
List<? extends Number>
- 13 -
bezeichnet daher logischerweise die Familie aller Instanziierungen des parametrisierten
Typs List, bei dem ein Typ aus der Familie der Subtypen von Number für den
Typparameter eingesetzt wurde, also List<Long>, List<Integer>, usw. inklusive
List<Number>. Ein Wildcard wie:
<? super Long>
bezeichnet die Familie aller Supertypen von Long ohne Long an sich selbst und somit
auch ein gebundenes Wildcard, beziehungsweise auch parametrisiertes Wildcard
genannt. Das Wildcard
<?>
steht für die Menge aller Typen ohne irgendwelche Einschränkungen. Dieses Wildcard
wird auch „unbounded wildcard“ genannt, was somit wie in der Version von Java 1.4 der
Verwendung ohne Generics entspricht. Durch die explizite Angabe jedoch erkennt der
Compiler, dass dieses Vorgehen (keine genaue Angabe des Typs) gewünscht ist und gibt
keine Warnung aus.
Eine Wildcard-Instanziierung eines parametrisierten Typs kann nicht dazu verwendet
werden, ein Objekt zu erzeugen. Es ist zwar möglich ein Objekt vom Typ List<?
extends Number> zu deklarieren, aber es ist nicht möglich ein Objekt vom Typ
List<? extends Number> zu initialisieren. Ein nicht initialisiertes Objekt vom Typ
List<? extends Number> kann aber auf Objekte von kompatiblen Typen verweisen.
Schachtel<?> kiste;
//ist gültig
kiste = new Schachtel<Apfel>(); //Objekt wird initialisiert
Schachtel<Apfel> apfelKiste = new Schachtel<?>(); //ist nicht
gültig
Der letzte Aufruf ist nicht gültig, da der Compiler keinen freien Willen hat, deswegen
kann er keine „beliebige“ Schachtel erzeugen.
Die kompatiblen Typen sind genau die Typen aus der Familie von Typen, die die
Wildcard-Instanziierung mit extends oder super bezeichnet. Ein Referenzobjekt vom
Typ List<? extends Number> kann also auf ein Objekt vom Typ List<Long> oder
List<Integer> usw. verweisen.
Analog kann ein Referenzobjekt vom Typ List<? super Long> auf Objekte vom Typ
List<Long>, List<Number> oder List<Comparable> usw. verweisen. Ein Objekt
vom Typ List<?> kann auf beliebige Instanziierungen von List verweisen.
Um die Verwendungsmöglichkeiten der Wildcards zusammen mit den Einschränkungen
etwas ansehnlicher darzustellen, ist ein kleines Beispiel aus dem Alltag notwendig.
Gehen wir davon aus, dass es eine Klasse Fahrzeuge gibt. Eine abgeleitete Klasse ist
dann die Klasse Auto. Als Liste definieren wir eine Garage, in der Fahrzeuge parken
können.
- 14 -
Setzen wir nun die Generics ein, so kann eine Garage die von der Klasse Fahrzeuge
instanziiert wurde alle Objekte vom Typ Fahrzeug aufnehmen. Das heißt, dass
Fahrzeuge vom Typ Fahrzeug in dieser Garage parken können.
public void parken(<Fahrzeug> was)
{
garage.add(was);
// Code zum Einfügen eines Fahrzeugs(wo) in Fahrzeugliste
…
}
Nun vermutet man, dass es möglich ist, in dieser Garage ein Auto zu parken, da es ja ein
Fahrzeug ist. Genau dies aber lässt Java nicht zu, um das Konzept der Generics nicht zu
unterwandern. Um nun aber in dieser Garage auch Autos bzw. andere Fahrzeuge, die
von der Klasse Fahrzeug abgeleitet sind, zu parken, müssen die Wildcards verwendet
werden. Dies funktioniert mit dem Schlüsselwort extends. Die Wildcards erlauben es,
beliebige Objekte aufzunehmen. Wie beliebig, hängt von der Einschränkung ab.
public void parken(<? extends Fahrzeug> was)
{
garage.add(was);
// Code zum Einfügen eines Fahrzeugs(wo) in Fahrzeugliste
…
}
Nun darf die Garage auch Autos und andere Fahrzeuge aufnehmen, die nicht direkt vom
Typ Fahrzeug sind.
Parametrisierte Methode oder Einsatz von Wildcards?
Es können verschiedene Möglichkeiten zur Definition einer Methode mit generischen
Datentypen bestehen. Es kann eine parametrisierte Methode erstellt werden oder
Wildcards eingesetzt werden:
static <T extends Comparable> List<T> sortList(List<T> list);
static List<? extends Comparable> sortList(List<? extends
Comparable> list);
Welche Möglichkeit sollte aber nun verwendet werden? Normalerweise sollte die
Möglichkeit mit den Wildcards eingesetzt werden. Nur wenn der Typ auch innerhalb der
Methode bzw. auch als Rückgabetyp benötigt wird, sollte die parametrisierte Methode
verwendet werden.
- 15 -
1.6 Änderungen an der Collection API
Durch die Neuerungen in Java in Bezug auf die Generics musste die Collection API auch
auf die neuen Möglichkeiten angepasst werden. Alle Funktionen in der Collection API
unterstützen nun die Generics und erlauben dem Programmierer somit, einen bedeutend
sichereren Code als früher zu schreiben.
Dies war nicht immer ganz einfach, da die Abwärtskompatibilität vollkommen
gewährleistet bleiben sollte. Dabei mussten einige spezielle Dinge beachtet werden. Ein
Beispiel ist die Methode Collections.max, die das größte Element in einer Collection
zurückgibt. Die traditionelle Signatur dieser Methode ist wie folgt definiert:
public class Collections
{
public static Object max(Collection coll);
}
Um diese Methode max nun mit den generischen Typen und vollständig kompatibel zur
ursprünglichen Methode zu machen, ist folgende nun folgende Definition in Java 5.0
aktuell:
public static <T extends Object & Comparable<? super T>>
T max(Collection<T> coll)
Mit dem &-Zeichen getrennt, kann man mehrere Bounds für den Parametertyp angeben,
der jeweils erste wird immer für die tatsächliche Implementierung, also für die interne
Generierung des gelöschten Typs, verwendet.
In die neue Collection API fanden aber auch einige neue Pakete, Klassen und
Schnittstellen Einzug. Im Folgenden eine kleine Auflistung. Nähere Informationen
können der Dokumentation zu Java (Javadoc) entnommen werden.
Die wichtigsten hinzugekommene Pakete sind:
•
java.lang.annotation (Gibt Bibliothekunterstützung für den Java NotationsService)
•
java.lang.instrument (Stellt Dienste für die JVM zur Verfügung)
•
java.lang.management (Stellt die Managementschnittstelle für die Überwachung
und für das Management der JVM sowie dem Betriebssystem zur Verfügung,
auf dem die JVM läuft)
•
java.util.concurrent
•
javax.sql.rowset (Standard-Interfaces und Klassen für JDBC RowSet)
•
javax.xml.xpath (beinhaltet ein Objektmodell zur Auswertung von XPathAusdrücken)
- 16 -
Wichtige hinzugekommene Klassen und Schnittstellen
•
Closeable (Interface)
•
Appendable (Interface)
•
Iterable (Interface)
•
ProcessBuilder
•
Type, TypeVariable (Interface)
•
StringBuilder (die nicht-synchronisierte Variante von StringBuffer)
•
Formatter (Java 2 Platform SE v1.5.0)
•
Queue (Interface)
•
UUID (universeller einmaliger Identifier)
•
MouseInfo
•
PointerInfo
- 17 -
1.7 Mögliche Fehlerquellen beim Umgang mit Generics
Da die Einführung der Generics in Java nur darauf beruht, dem Compiler verschiedene
Anweisungen zur Behandlung verschiedener Objekte zu überwachen und prüfen, gibt es
auch einige Sprachkonstrukte, die nicht erlaubt sind, aber nicht als solche sofort
erkennbar sind.
Generische Typen sind in statischem Kontext nicht erlaubt:
class Test<T>
{
static T meineKonstante;
// Fehler
static T meineRueckgabe()
{
…
} // Fehler
static void meineMethode(List<T> liste)
{
…
} // Fehler
}
Da die generischen Typen nur auf ihre schon aus älteren Java-Programmen bekannten
Objekten zurückgeführt werden, machen solche Aufrufe schlichtweg keinen Sinn.
Genauso dürfen Klassen bzw. Methoden mit generischen Typparametern nicht überladen
werden:
class Versuch<T, U>
{
void setze(T erster)
{
…
}
void setze(U zweiter)
{
…
} // Fehler
}
- 18 -
2
Sonstige Erneuerungen
In diesem Kapitel wird auf die Spracherweiterungen von Java 5.0 eingegangen, die
neben der generischen Programmierung eine wichtige Bedeutung spielen.
2.1 Vereinfachte Syntax für die Iteration
Die for-Schleife ist erheblich erweitert worden. Es ist möglich, mit einer einfacheren
Syntax durch verschiedene Strukturen zu iterieren. Dies gilt unter anderem für Arrays
und Collections1.
Ein Beispiel für das Iterieren durch Arrays
Auf die herkömmliche Weise:
public static void printArray1(int[] args)
{
for (int i = 0; i < args.length; ++i)
{
System.out.println(args[i]);
}
}
Erweiterte for-Schleife:
public static void printArray1(int[] args)
{
for (int i : args)
{
System.out.println(i);
}
}
Das Erzeugen und Inkrementieren der Hilfsvariablen i, das Herausfinden des ArrayEnde und der explizite Zugriff auf die Array-Variable kann mit Hilfe der erweiterten
for-Schleife eingespart werden. Der Code wird einfacher und klarer.
1
Eine Collection bezeichnet Datenstrukturen wie Listen, Stacks, Schlangen, Bäume und Mengen im
mathematischen Sinne
- 19 -
Ein Beispiel für das Iterieren durch Collections:
Auf die herkömmliche Weise:
public static void printVector1(Vector v)
{
for (Iterator it = v.iterator(); it.hasNext(); )
{
Object o = it.next();
System.out.println(o);
}
}
Erweiterte for-Schleife:
public static void printVector1(Vector v)
{
for (Object 0 : v)
{
System.out.println(o);
}
}
In der neuen Variante kann mit Hilfe der erweiterten for-Schleife auf die explizite
Deklaration und Verwendung des Iterators2 verzichtet werden. Beide Varianten haben
das gleiche Ergebnis, nur ist der zweite Code einfacher zu lesen und intuitiv zu
verstehen. Der Doppelpunkt ist als „in“ zu lesen. Wie bei der normalen for-Schleife
kann man in der Schleife Variablen deklarieren, wie hier in der Variante 1.
Greift man auf die neue for-Schleife zurück und kombiniert das Ganze mit der
Verwendung von Generics, kann eine Menge Code eingespart werden. Die so
„optimierten“ for-Schleifen sind auch besser lesbar. Der Compiler übernimmt hierbei
die gesamte Arbeit, um die neue Schleifenart zu implementieren. Dabei wird der Code
wieder in die Darstellung der herkömmlichen Weise umgewandelt.
Die neue for-Schleife schützt auch vor Fehlern, die leicht bei der herkömmlichen forSchleife auftreten können. Zum Beispiel verursacht
for(int i = 0; i < x.length; i++)
{
for(int j = 0; j < x.length; j++)
{
System.out.println(x[i] * y[j]);
}
}
2
Ein Iterator ist ein Interface zum Durchlaufen der Elemente einer Collection
- 20 -
einen Fehler bei der Index-Obergrenze des Feldes y, da die Schleifenvariable j, die auch
als Index für y verwendet wird, tatsächlich über die Länge des Arrays x läuft. Solche
Fehler werden mit Hilfe der neuen for-Schleife verhindert.
2.2 Autoboxing / Autounboxing
Ein Nachteil der Collections war bisher, dass sie nur Elemente von komplexen
Datentypen aufnehmen konnten. Wollte man Elemente einfacher Datentypen wie int,
long, char etc. einfügen, war bisher eine passende Wrapper-Instanz notwendig. Beim
Entnehmen mussten die Elemente manuell „entpackt“ werden.
Ab Java 5.0 übernimmt der Compiler die Aufgabe, primitive Datentypen in ihre Wrapper
und umgekehrt zu konvertieren. Die Vorgehensweise wird auch Autoboxing genannt, da
der Compiler einen Grunddatentyp automatisch in einen Objekttyp umwandelt, so als ob
er diesen in eine spezielle Schachtel verpacken würde. Der umgekehrte Weg wird mit
Unboxing bezeichnet. Dadurch dass der Compiler die notwendigen Konvertierungen
übernimmt, werden Fehler vermieden, die erst zur Laufzeit auftreten würden, wenn ein
Objekt auf einen falschen Datentyp gecastet wird.
Beispiel von Boxing und Autoboxing:
int i = 111;
Integer j = i;
int k = j;
//Boxing:
j = Integer.valueOf(i)
//Unboxing: k = j.intValue()
Ein weiteres Beispiel:
Auf die herkömmliche Weise:
list.add(0, new Integer(59));
int n = ((Integer)(list.get(0))).intValue();
Autoboxing / Autounboxing:
Durch das Auto(un)boxing fällt diese manuelle Konvertierung weg. Das oben
angegebene Codebeispiel kann somit folgendermaßen vereinfacht werden:
list.add(0, 59);
int total = list.get(0);
- 21 -
2.3 Syntaktische Unterstützung für Aufzählungswerte (Enumerations)
Bisher wurden Aufzählungen über verschiedene Implementierungen auf Klassenebene
gelöst. Allerdings gab es dabei oft Einbußen in der Performance und es muss häufig
derselbe Code geschrieben werden. Java 5 ermöglicht richtige Aufzählungen, mit Hilfe
des Schlüsselwortes enum, welches früher schon reserviert, aber nicht belegt war.
Enumeration ist ein Datentyp mit einem endlichen Wertebereich. Alle Werte des
Aufzählungstyps werden bei der Deklaration des Typs als Name definiert. Man
unterscheidet typenlose Aufzählungen, die lediglich Namen für numerische Werte
festlegen, und typsichere Aufzählungen. Typsichere Aufzählungen verhindern, dass
„Äpfel mit Birnen“ verglichen werden. Typesafe Enumerations (enum) besitzen im
einfachsten Fall einen Namen und bestehen aus einer Element-Menge dieses Typs
wodurch Typsicherheit erreicht wird.
Die einzelnen Werte der Konstanten des enum lassen sich mittels der Methode valueOf
ermitteln. Benötigt man eine Aufzählung aller Werte des enum, so bietet sich die
Klassenmethode values an. Enums sind fast vollwertige Klassen, so lassen sich neben
den oben aufgeführten Methoden weitere Objekt- und Klassenmethoden sowie Objektund Klassenattribute frei definieren. Die Haupteinschränkung gegenüber Klassen besteht
darin, dass man weder auf Merkmale der Oberklassen zugreifen kann, noch dass man
eine Klassenhierarchie mittels enums aufbauen darf. Ein enum darf keine Unterklasse
besitzen.
Besondere Bedeutung finden enums in switch/case-Anweisungen, denn dort können
hinter dem Schlüsselwort case nur Werte verwendet werden, die sich implizit in intWerte wandeln lassen. Da diese wenig aussagekräftig sind, verwendeten Entwickler oft
int-Werte, die als statische Konstanten unter sprechenden Namen in einer Klasse
definiert sind. Die so definierten int-Enumerations haben einige Nachteile:
•
Sie bieten keine Typsicherheit, da die Verwendung der Konstanten nicht
überprüft wird
•
Sie bilden keinen „richtigen“ Namensraum
•
Sie können nicht mit Collection-Instanzen verwendet werden
•
Bei Ausgabe werden die int-Werte und nicht die eigentlich gewünschten
Konstantennamen ausgegeben
Diese Nachteile lassen sich durch den Einsatz des Typsafe enum Entwurfsmusters
umgehen. Enum-Konstanten sind in switch-Anweisungen möglich, da sie intern über
eine Ganzzahl als Identifikator verfügen, der vom Compiler eingesetzt wird.
- 22 -
Vorteile von enum:
•
enums bieten Typsicherheit zur Übersetzungszeit
•
enum-Konstanten können gelöscht, hinzugefügt oder umsortiert werden, ohne
dass die sie verwendenden Klassen neu übersetzt werden müssen.
•
enums lassen sich in switch/case-Anweisungen verwenden.
Beispiel
public enum Farbe {
rot, gelb, weiss, violett
};
Da es sich bei einer typesafe Enumeration im Grunde um eine Klasse handelt, können
innerhalb einer typesafe Enumeration zusätzlich Attribute, Methoden und parametrisierte
Konstruktoren definiert werden. Damit ist es möglich den Elementen weitere
Eigenschaften zuzuweisen.
Typesafe Enumeration mit Konstruktor, Methoden und Attributen:
public enum Blume
{
Rose("Topf"), Akelei("Garten"), Mohn("Feld");
Blume(String standort)
{
this.standort = standort;
}
private final String standort;
public String getStandort()
{
return standort;
}
};
Bei der Implementierung von typesafe Enumerations gibt es aber auch ein paar Regeln
zu beachten:
•
Ein Defaultkonstruktor darf nicht implementiert werden, da dieser implizit
bereitgestellt wird
•
Die Typesafe Enumeration besitzt automatisch die beiden Methoden values()
und valueof(String) sowie die Methoden der Klasse java.lang.enum
•
Die Klasse enum ist implizit immer statisch
•
Deklarierte Attribute und Methoden sind automatisch statisch
- 23 -
2.4 Statischer Import
Fast jede Java-Anwendung verwendet Konstanten. Diese sind oft zentral in einer Klasse
statisch definiert. Das hat den Nachteil, dass bei der Nutzung der Klassenname
vorangestellt werden muss. Bisher konnte dieses Problem mit Hilfe eines Interfaces
gelöst werden, welches die Konstanten definiert, ansonsten aber nichts deklariert.
Die Klassen die diese Konstanten nutzen wollen, mussten das Interface implementieren.
Dadurch können die Konstanten ohne vorangestellten Klassennamen genutzt werden.
Diese Lösung funktioniert, ist aber aus sprachtechnischer Sicht zu verwerfen, denn
Interfaces dienen der Deklaration neuer Typen und nicht als Konstanten-Container.
Die import static Deklaration ist der normalen import-Anweisung sehr ähnlich.
Während import die Einbindung von Klassen ohne die Qualifizierung des Paketnamens
ermöglicht, erleichtert import static die Einbindung von statischen Mitgliedern einer
Klasse ohne die Qualifizierung des Klassennamens.
Beispiel auf die bisherige Weise:
package com.name;
interface XYZ
{
public static final double Constant1 = someValue;
public static final double Constant2 = anotherValue;
}
//Verwendung der Konstanten des XYZ-Interface
public class MyClass implements XYZ
{
…
double value = 2* Constant1;
…
}
Beispiel mit der Spracherweiterung import static:
import static com.name.XYZ.*;
public class MyClass
{
…
double value = 2 * Constant1;
…
}
Ein weiteres Beispiel:
import static java.lang.Math.*;
import static java.lang.System.*;
- 24 -
Mit Hilfe dieser zwei statischen Imports kann man sin(x) anstatt Math.sin(x) und
Welt") anstatt System.out.println("Hallo Welt")
verwenden.
out.println("Hallo
Fehlerquellen bei der Verwendung von import static:
Die Überlagerung von statischen Importen mit Methoden oder Feldern der Klasse kann
zu Fehlern führen. Hierfür ist festgelegt, dass ein statischer Import nie eine interne
Definition der Klasse überschreibt. Der Compiler meldet aber keinen Fehler. Dadurch
kann es besonders bei Wartungsarbeiten an einer Klasse unklar sein, welche Methode
gerade benutzt wird und falsche Annahmen getroffen werden.
Der statische Import sollte sparsam verwendet werden, zum Beispiel in Fällen, in denen
man geneigt wäre lokale Kopien von Konstanten anzulegen oder Konstanten in
Schnittstellen zu deklarieren, um diese später durch Vererbung einzubinden. Was auch
bedacht werden sollte ist, dass ein übermäßiger Gebrauch von statischen Importen zu
unlesbaren Programmen führen kann. Das ist aber genau das Gegenteil wofür die
statischen Importe eingeführt wurden.
- 25 -
2.5 Erweitertes Look and Feel
Das Standard-Java-Look&Feel wird in der J2SE 5.0 beispielsweise durch das Ocean
L&F abgelöst, was keine völlig neue, aber dennoch angepasste Darstellung der
grafischen Elemente zeigt. Zusätzlich gibt es noch das anpassbare Synth L&F. Der
Vorteil gegenüber anderen L&F ist bei Synth, dass es nicht über Programmierung
sondern durch eine XML-Datei konfigurierbar ist und leicht durch zusätzliche Skins
erweiterbar ist.
Die Klasse JTable wurde mit einer Druckunterstützung ausgerüstet und kleinere
Änderungen von Komponenten können direkt über die Methode add() in ein JFrame
eingefügt werden.
Beispiel:
JFrame jf = new JFrame();
JButton jb = new JButton();
jf.add(jb);
Java 2D wurde um die Hardware-Unterstützung von OpenGL erweitert. Das Image I/O
API unterstützt zusätzlich lesend und schreibend die Grafikformate BMP und WBMP.
Das AWT enthält neue Methoden zur Ermittlung der aktuellen Mausposition, zum
dauerhaften Setzen von Fenstern in den Vordergrund und dem Setzen der
Standardposition eines neuen Fensters.
Für Java unter Unix wurde die Oberflächenbibliothek AWT für das X Window System
überarbeitet. Das neue XAWT malt seine Bildschirmelemente selbst und ist nicht mehr
von den Motif- und Xt-Bibliotheken abhängig. Unter Linux soll das neues XAWT GUIAnwendungen um den Faktor 6,5 beschleunigen.
- 26 -
2.6 Annotations – Metadaten im Quellcode
Annotations ermöglichen es, Java-Code mit zusätzlichen Informationen anzureichern,
die z.B. für andere Anwendungen nützlich sind. Die Annotations ermöglichen dem
Entwickler eigene Tags zu definieren, mit denen eigene Klassen, Attribute und
Methoden für ein spezielles Werkzeug gekennzeichnet werden können. AnnotationsTags beginnen immer mit dem @-Zeichen. Durch die Verwendung von Annotations
lassen sich zur Compile-Zeit und zur Laufzeit über die class-Dateien automatisiert
Informationen auslesen.
Der Vorteil von Annotationen gegenüber anderen Möglichkeiten der Bereitstellung von
Informationen liegt in der fest definierten Funktionsweise und den Zugriffsmöglichkeiten über ein API. Über Annotationen erhält man einen Mechanismus um
Informationen zur automatisierten Auswertung weiterzugeben.
Es gibt bereits Metadaten in Java. Ein Beispiel dafür ist JavaDoc. Mit JavaDocKommentaren kann beispielsweise die Versionsnummer und der Name des Autors einer
Klasse angegeben werden. Beispiel zu JavaDoc:
/**
@version 1.0
@author Mustermann
*/
public class JavaDoc
{
…
}
Anstatt nur auf vordefinierte Felder wie Autor oder Version zurückgreifen zu können,
kann der Benutzer seit Java 5.0 die Felder selbst definieren. Die Struktur der Metadaten
wird wie ein Java Interface beschrieben, das den Typ der Annotation festlegt. Dem
interface-Schlüsselwort wird ein @ vorangestellt, um Metadaten von herkömmlichen
Schnittstellen zu unterscheiden. Soll die Annotation Parameter besitzen, muss für jeden
Parameter eine Methode mit dem entsprechenden Rückgabewert erzeugt werden. Der
Name der Methode entspricht dabei dem Namen des Parameters.
Bei der Deklaration von Metadaten sind folgende Einschränkungen zu beachten:
•
Es ist nicht erlaubt, Metadatenstrukturen zu vererben. Jede Struktur erbt implizit
von der Schnittstelle java.lang.annotation.Annotation.
•
Die Methoden dürfen keine Parameter haben
•
Die Methoden dürfen als Rückgabetyp lediglich primitive Typen wie int oder
float, String, Class, Enumeration, Annotationstypen und Arrays der
vorigen Typen verwenden. Außerdem sind auch generische Methoden verboten.
•
Methoden können keine Exceptions werfen. Insbesondere ist also die
Verwendung von throws verboten.
- 27 -
• Annotationstypen können nicht parametrisiert werden.
Mit Hilfe der Reflection API kann man Annotations auch zur Laufzeit einer Anwendung
auswerten.
Anwendungsbeispiele:
•
Annotations können Informationen für andere Anwendungen bereitstellen. Die
externen Anwendungen generieren aufgrund der Informationen der Annotations
Interfaces und/oder Klassen
•
Jeder Entwickler kann über Annotations für eine Methode, Klasse usw.
Informationen hinterlegen. Diese können automatisiert gesammelt und
ausgewertet werden.
•
Erzeugen automatischer Todo-Listen
Beispiel:
import java.lang.reflect.*;
import java.lang.annotation.*; //Unterstütz. für die Annotat.
public class TodoAnnotationTest
{
@Todo(beschreibung={"Beispiel mit Annotations",
klasse = TodoAnnotationTest.class, wertigkeit=3,
bem=@Bemerkung("keine"))
public static void main(String args[])
{
Methode[] methoden =
TodoAnnotationTest.class.getMethods();
for(Method m : methoden)
{
if(m.isAnnotationPresent(Todo.class))
{
System.out.println("Methoden " + m.getName());
Todo td = m.getAnnotation(Todo.class);
System.out.println(td.beschreibung()[0]);
System.out.println(td.wertigkeit());
System.out.println(td.bem().value());
}
}
}
}
Annotationen werden in Zukunft eine große Rolle bei der dynamischen Generierung von
Java-Quellcode spielen. Durch die einfache Beschreibung der benötigten Informationen
über Annotationen wird sich der Aufwand auf Entwicklerseite verringern. Außerdem
wird der Java-Quellcode weniger fehleranfällig und besser lesbar sein. Allerdings ist zu
beachten, dass weder der Java-Quelltext noch die class-Dateien kompatibel mit älteren
Java-Versionen sind.
- 28 -
2.7 Variable Parameterübergabe
Unter variabler Parameterübergabe versteht man, dass beim Aufruf einer Methode eine
beliebige Anzahl und beliebige Typen von Parametern übergeben werden können. Die
Idee bei variabler Parameterübergabe besteht darin, eine einzige Methode zu
implementieren, die eine beliebige Anzahl von Datentypen von Argumenten
entgegennehmen kann. Ein Paradebeispiel für variable Parameterübergabe ist die
Methode printf aus der Programmiersprache C.
Die Zeichenkette „…“ dient dem Übersetzer als Schlüsselwort, dass eine variable
Anzahl von Parametern folgt. Beim Aufruf werden nun alle folgenden Parameter in ein
Array gepackt und der Methode als Parameter übergeben. In der Implementierung der
Methode wird dieses Array Element für Element wieder ausgelesen. Die variable
Parameterübergabe ist eine reine Compilerleistung, die dem Programmierer das Leben
sehr vereinfacht.
Beispiel:
class Computer {
public static int sum (int … values) {…}
}
Aufruf dieser Methode:
int x = Computer.sum(1,2);
int y = Computer.sum(1,2,3);
Was passiert, wenn man neben der Methode mit variabler Anzahl von Objekten noch die
folgenden Methoden implementiert?
public static void print(Object … args);
public static void print(Object arg);
public static void print(Integer … args);
Da alle Methoden unterschiedliche Signaturen besitzen, lässt sich der Programmtext
problemlos übersetzen. Aufrufbeispiele und die zur Laufzeit aufgerufene Methode:
print(5, 7.0, "35,4");
print("35,4");
print(5, 7, 15);
print(5);
//print(Object … args)
//print(Object arg)
//print(Integer … args)
//print(Object arg)
Es wird immer die Methode mit der am besten passenden Signatur aufgerufen, wobei die
Anzahl der Parameter eine höhere Priorität besitzt als der Datentyp des Parameters.
- 29 -
3
Zusammenfassung
Java 5.0 bringt nicht nur neue Klassenbibliotheken und ein verbessertes
Laufzeitverhalten mit sich, sondern erweitert auch die Sprachsyntax umfangreich. Hier
zahlt sich die Flexibilität des Java-Interpreters und der Laufzeitumgebung aus. Sie
gestattet es auch komplexe sprachliche Neuerungen aufzunehmen ohne einen Umbau in
der Basis zu fordern. Die neuen Sprachmittel bieten viele Möglichkeiten, die die JavaProgrammierung ausdrucksstärker und einfacher machen.
Mit den Generics erübrigt sich die lästige Typumwandlung von Objekten, die zusammen
mit abstrakten Datentypen verwendet werden. Dies macht nicht nur den Code einfacher
zu lesen, sondern erspart dem Entwickler auch einige Laufzeitprobleme, die nun schon
vom Compiler entdeckt werden können. Allerdings ist Vorsicht im Umgang mit den
Generics geboten, denn es können schnell Laufzeitprobleme entstehen, wenn häufige
Casts ausgeführt werden, die im Quellcode nicht mehr direkt ersichtlich sind.
Die neue for-Schleife erspart Tipparbeit und schützt vor Fehlern. Autoboxing und
Unboxing ersparen einem zwar nicht Laufzeitfehler, aber es erleichtert die
Programmierarbeit, da die manuelle Konvertierung zwischen primitiven Datentypen und
ihren Wrapper-Klassen wegfällt.
Wie auch Generics geben Enumerationen dem Entwickler mehr Typsicherheit. Durch
die variable Parameterübergabe ist es nicht mehr nötig mit Listen oder Arrays von
Parametern zu arbeiten. Statische Imports ersparen dem Entwickler ebenso wie die forSchleife und das Autoboxing Tipparbeit.
Der fest in der Java-Syntax verankerte Annotationstyp erlaubt dem Entwickler beliebige
Metadaten zu definieren, ohne sich auf die Verwaltungsinfrastruktur eines Drittanbieters
stützen zu müssen.
Zusammenfassend lässt sich sagen, dass die Erweiterungen Java zu einer wesentlich
mächtigeren, einfacheren und vor allem robusteren Sprache machen. Trotzdem muss
noch viel Arbeit geleistet werden, um die neue Komplexität zum Beispiel durch die
Generics und die Fehlerpotenziale wie zum Beispiel beim Autoboxing und statischer
Importe in den Griff zu bekommen.
- 30 -
4
Literaturverzeichnis
[AL05]
Angelika Langer: Generics, Auflage vom 2. September 2005
http://www.angelikalanger.com/GenericsFAQ/FAQSections/Parameterized
Types.html#WIldcard%20Instantiations, aufgerufen im Oktober 2005.
[BF05]
Böttcher, Ulrike; Frischalowski Dirk: Java 5 - Programmierhandbuch,
2005.
[BS04]
Bernhard Steppan: Schneller programmieren mit Java 5.0, Computerwoche
43/2004, Seite 32.
[BU04]
Burd Barry: Java2 für Dummies. Mitp-Verlag/Bonn, 4. Auflage 2004.
[CEH+05]
Chandrasekhara Vasus, Dr. Andreas Eberhart, Dr. Horst Hellbrück, Stefan
Kraus, Dr. Ulrich Walther: Java 5.0 Konzepte, Grundlagen und
Erweiterungen in 5.0, Carl Hanser Verlag München Wien, 2005.
[CU04]
Christian Ullenboom: Erste Neuerungen in Java 1.5.
[JA05]
Josef Adersberger: Mit dem Tiger in der Falle, Java Magazin Ausgabe
8.2005.
[JH03]
Jason Hunter: Big Changes Coming for Java, Oracel Magazine
September/October 2003.
[NW05]
Maurice Naftalin, Philip Wadler: Generics and Collections in Java 5,
O’Reilly Media, Inc. 2005.
[QHM04]
Qusay H. Mahmoud: The All-New Java 2 Platform, Standard Edition
(J2SE) 5.0, http://java.sun.com/, aufgerufen im September 2005
[UB03]
Dr. Ulrich Breymann: Tiger auf dem Sprung, c’t 2003, Heft 26, Heise
Zeitschriften Verlag GmbH & Co.KG.
[VS05]
Venkat Subramaniam: Generics in Java – Part I
http://www.agiledeveloper.com/, aufgerufen im Oktober 2005.
- 31 -
bis
III,
Herunterladen