© Excellent backgrounds/Shutterstock.com
Vom JDK 1.0 bis Java 9

20 Jahre Java - Rückblick in 22 Listings


Zu so einem runden Geburtstag gehört einfach ein Rückblick dazu. Java hat sich in den zwei Jahrzehnten seit seiner Entstehung stark verändert. Dieser Artikel riskiert einen solchen nostalgischen Rückblick auf die verschiedenen Releases mit ihren Eigenheiten, Stärken und Schwächen. Er spiegelt meine persönlichen Erlebnisse und Schwerpunkte durch die Jahre wider und erhebt ausdrücklich keinen Anspruch auf Vollständigkeit.

Bei einigen der Codebeispiele sprengt eine detaillierte Erklärung den Rahmen dieses Rückblicks. Das sollte niemanden verschrecken – in diesen Fällen kann man die Listings einfach als historische Kuriosität betrachten. Und wenn das eine oder andere Feature die Neugier weckt, gibt es in jedem Fall eine detaillierte Dokumentation im Internet.

Die Anfänge – JDK 1.0

Java war ursprünglich als Sprache für das Web gedacht – es entstand in einer Zeit, wo Browser als neues Betriebssystem diskutiert wurden und überhaupt alle Welt von portablen Clientanwendungen träumte.

Zunächst waren Applets stark im Fokus von Java (Listing 1) – das Ausführen von beliebigen Anwendungen im Browser. Deshalb gab es von Anfang an auch den SecurityManager-Mechanismus, mit dem man Java-Anwendungen in einer Sandbox („Sandkiste“) mit konfigurierbaren Berechtigungen laufen lassen konnte (Listing 2).

Listing 1

<applet code = "my.very.nifty.AppletClass" archive = "MyCode.jar" width = 500 height = 300> <param name="permissions" value="sandbox" /> </applet>

Listing 2

SecurityManager security = System.getSecurityManager(); if (security != null) { // Beispielhaft überprüfen, ob eine Berechtigung zum Lesen  // dieser speziellen System-Property vorliegt security.checkPropertyAccess ("my.special.property"); }

Außerdem konnte Java von Anfang an Multi-Thread­ing. Das wirkt heute selbstverständlich, aber Mitte der neunziger Jahre war es etwas ziemlich Besonderes, betriebssystemunabhängig mit Threads programmieren zu können. Java hatte von Anfang an mit dem Schlüsselwort synchronized auch Mechanismen zum Koordinieren von Threads als Teil der Sprache selbst.

Als Collection-Klassen gab es im Wesentlichen Vector und Hashtable. Beide waren als Ausdruck der Multi-Threading-Euphorie der Zeit voll synchronisiert (und sind es wegen der Rückwärtskompatibilität immer noch, was sie weitgehend zur Bedeutungslosigkeit verdammt hat). Der Umgang mit Collections war ziemlich spröde (Listing 3) – ein Blick auf den Code hilft dabei, Dinge wie Generics, vereinfachte for-Schleifen-Syntax, Typinferenz und ähnlichen später hinzugekommenen syntaktischen Zucker zu würdigen. Das Iterieren über einen Vector erfolgte über eine Enumeration, das Iterator-Interface kam erst später dazu.

Listing 3

// Collections in Java 1.0 // Vector als einzige Collection- // Klasse Vector v = new Vector(); v.addElement ("Happy"); v.addElement ("birthday"); v.addElement ("Java!"); // Enumeration statt Iterator Enumeration en = v.elements (); while (en.hasMoreElements()) { String word = (String) en.nextElement (); System.out.println (word); }

In der Standardbibliothek der ersten Tage gab es an einigen Stellen Designentscheidungen, die teilweise viele Jahre lang ein Stolperstein für Entwickler waren. Die Klasse java.util.Stack erbt zum Beispiel von Vector, sodass man an beliebigen Stellen Elemente hinzufügen oder entfernen kann.

Mein persönliches Lieblingsbeispiel für Designfehler, mit denen man lange leben musste, ist aber die Klasse java.util.Date (Listing 4). Sie repräsentiert einen Zeitstempel, der intern als Anzahl der Millisekunden seit Jahresbeginn 1970 UTC abgelegt wird. Dieses Feld ist aber nicht final, es gibt auch eine Methode setTime(). Man muss Date-Objekte also entweder defensiv kopieren, bevor man sie herumreicht, oder hoffen, dass niemand ihren internen Zustand verändert.

Außerdem gibt es Methoden get/setDay(), get/setMonth() und get/setYear(), mit denen man ein Date-Objekt als Kalenderdatum behandeln kann. Eine Klasse kann aber nicht sowohl einen Zeitstempel als auch ein Kalenderdatum repräsentieren – es kann in Indien schon Dienstag sein, während es in den USA gleichzeitig noch Montag ist. Die Datumsmethoden sind zum Glück seit Java 1.1 deprecated, wegen der konsequenten Rückwärtskompatibilität werden sie aber wohl auch in Zukunft nicht entfernt werden.

Listing 4

public class Date { private transient long fastTime; public Date(long date) { fastTime = date; } public Date(int year, int month, int date, int hrs, int min, int sec) { int y = year + 1900; if (month >= 12) { y += month / 12; month %= 12; } else if (month < 0) { y += CalendarUtils.floorDivide(month, 12); month = CalendarUtils.mod(month, 12); } ... } public long getTime() { ... return fastTime; } public void setTime(long time) { fastTime = time; ... } ... // getYear(), setYear(), getMonth(),   // setMonth(), ... }

Insgesamt ist Java 1.0 ein respektabler Start mit innovativen Ansätzen, der aber gleichzeitig eine Reihe von Kinderkrankheiten hatte.

JDK 1.1: 1997

Kurz nach dem ursprünglichen Release kam das JDK 1.1 heraus. Es erweiterte die Standardbibliothek, und die JVM hatte – zumindest unter Windows – einen „Just-In-Time-Compiler“ (JIT), und zwar von Symantic. Heute sind wir gewohnt, dass eine JVM zur Laufzeit den Bytecode in Maschinencode übersetzt und den dann ausführt, aber das ist erst ab JDK 1.1 der Fall: ein großer Meilenstein für die Performance von Java.

Außerdem kamen mit Version 1.1 innere Klassen, und zwar sowohl benannte als auch anonyme, hinzu. Dadurch kann man z. B. durch idiomatische Verwendung anonymer Klassen Codeblöcke erzeugen und herumreichen, wenn auch mit erheblichem syntaktischen Ballast (Listing 5). Das ist quasi ein Vorläufer der Lambdas, die über fünfzehn Jahre später kamen.

Listing 5

new Thread (new Runnable() { public void run () { System.out.println ("Dies ist eine anonyme lokale " + "Klasse, die Runnable implementiert"); } }).start ();

Serverseitige Java-Anwendungen kamen auch mehr in den Fokus. So gab es mit dieser Version RMI („Remote Method Invocation“) ein Framework zur objektorientierten Kommunikation über Netzwerke. Ein Teil davon war ein Mechanismus, um beliebige Objekte in Bytefolgen zu konvertieren, sie zu serialisieren. Der ist heute sehr viel bekannter und bedeutender als RMI, weil er so einfach zu benutzen ist: Es genügt meist, dass alle beteiligten Klassen das Interface java.io.Serializable implementieren (mit readResolve() etc. für Sonderfälle), den Rest erledigen ObjectInputStream und ObjectOutputStream.

Listing 6 enthält eine Methode deepCopy(), die ein beliebiges Objekt mittels ObjectOutputStream in ein Byte-Array serialisiert und anschließend aus diesem Byte-Array mittels ObjectInputStream wieder ein Objekt erzeugt. Dieses neu erzeugte Objekt ist eine „tiefe“ Kopie des ursprünglichen Objekts, und Änderungen am einen sind vollständig isoliert vom anderen.

Listing 6

Object deepCopy (Serializable o) throws IOException, ClassNotFoundException { final ByteArrayOutputStream baos = new ByteArrayOutputStream (); final ObjectOutputStream oos = new ObjectOutputStream (baos); oos.writeObject (o); oos.close (); final byte[] bytes = baos.toByteArray (); final ByteArrayInputStream bais = new ByteArrayInputStream (bytes); final ObjectInputStream ois = new ObjectInputStream (bais); final Object result = ois.readObject (); ois.close (); return result; }

Der Serialisierungs- und Deserialisierungscode verwendet intern Reflection, genauer den Zugriff auf Methode...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Teams

Für Firmen haben wir individuelle Teamlizenzen. Wir erstellen Ihnen gerne ein passendes Angebot.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang