Entwicklung der Java-8-Unterstützung für Eclipse

Werkzeuge  –  3 Kommentare

Punktgenau zum Java-8-Release am 18. März 2014 hatten die Eclipse-Entwickler ihre Unterstützung für die neue Version der Programmiersprache veröffentlicht. Bei Betrachtung der Hintergründe des Release wird schnell klar, dass es sich für die Community um mehr als ein Update-Release handelte.

Wer sich mit Eclipse beschäftigt, mag wissen, dass im Zentrum diverser Entwicklungen das "Eclipse Project" steht. Darin sind die Kernkomponenten der Java-IDE zu finden, wie sie initial von IBM gestiftet wurden und seitdem weiterentwickelt werden. Bis vor kurzem galt, dass die Kernkomponenten und insbesondere die Java Development Tools (JDT) fest in der Hand von Big Blue waren und sich jeder darauf verließ, dass sich IBM schon weiter darum kümmern werde.

Was allerdings im März veröffentlicht wurde, war in dieser Form nur als Leistung der Community möglich. Die Aufgabe war riesig, das Team bei IBM aus diversen Gründen stark im Umbruch, und zeitweise war es gar nicht sicher, ob sich der Termin einhalten ließ. Das JDT-Team, das unter der Leitung von Srikanth Sankaran (IBM Bangalore) letztlich die Aufgabe gestemmt hat, umfasst auch drei Committer, die nicht bei IBM angestellt sind. Das ist ein wichtiges Signal für die Zukunft von Eclipse.

Es wäre aber falsch, an dieser Stelle nur auf die Committer zu schauen: Im vergangenen Jahr hatte JDT mehr Patches aus der Community erhalten als in "normalen" Zeiten, und ein wahrlich unverzichtbarer Beitrag kam in Form von Bug-Reports. Hier haben die vielen Alpha- und Beta-Tester nicht nur Probleme gemeldet, sondern durch knappe Testprogramme auch exzellente Vorarbeit zur Fehlerbehebung geleistet.

Der Eclipse Compiler for Java

Was mag schon groß dran sein, eine IDE von einer Java-Version auf die nächste umzustellen? Zwar sind einige Operationen in der JDT-Benutzungsoberfläche schon recht komplex (man denke z. B. an Refactorings), aber JDT kratzt nicht nur an der Oberfläche: Der Kern dieser IDE ist der Eclipse Compiler for Java (ECJ), und einen Compiler programmiert man nicht mal eben in der Frühstückspause.

Warum denn überhaupt die Mühe? Die Antwort lautet: zur inkrementellen Kompilierung und als Basis für UI-Features wie Code Assist, Suche und Refactorings. Inkrementelles Kompilieren bedeutet dabei

  • wirklich nur so viel zu kompilieren wie nötig,
  • schon während des Schreibens von Quelltext zu kompilieren, und
  • einzelne Statements zur interaktiven Auswertung zu kompilieren.

Alle UI-Features direkt mit den Informationen des Compilers zu versorgen, hat den großen Vorteil, dass sie den Quelltext genau in der semantisch analysierten Form sehen, die benutzt wird, um den ausführbaren Bytecode zu erzeugen.

Die Arbeit an Java 8 hat aber noch eine weitere, wichtige Rolle des Eclipse-Compilers ins Blickfeld gerückt: Da er als Alternativimplementierung zum Compiler javac ins Feld geschickt wird, leisten die Eclipse-Entwickler eine essenzielle Qualitätssicherung. Während viele Programmierer die Sprache Java aus dem Umgang mit javac lernen, basiert die Arbeit der Entwickler zunächst allein auf der Java Language Specification (JLS). Das ist eine Art Grundgesetz der Java-Entwicklung und die Bauanleitung für einen Java-Compiler.

Die Sache wird in dem Moment spannend, wo man die geplante Spezifikation eines neuen Java-Features nimmt, versucht, es zu implementieren, und feststellt, dass sich das so (unvollständig oder widersprüchlich spezifiziert) gar nicht implementieren lässt. Oder dass man das zwar so bauen kann, aber sich die Entwicklung dann nicht so wie versprochen verhält. Für das Kern-Feature von Java 8, die Lambda-Ausrücke, hat Daniel Smith von Oracle exzellente Arbeit beim Spezifizieren geleistet, aber die im März 2014 veröffentlichte Version der JLS ist auch das Ergebnis etlicher Bug-Reports und detaillierter Diskussionen, beeinflusst durch die Arbeiten an ECJ.

Zwei Stufen der Qualitätssicherung durch ECJ

Als die ECJ-Entwickler überzeugt davon waren, dass ihr Compiler ein bestimmtes Feature korrekt nach JLS implementiert, begann die nächste Runde der Qualitätssicherung: Zeigen javac und ECJ dasselbe Verhalten? Melden beide dieselben Fehler? Tun die übersetzten Programme dasselbe? Stärker noch als bei der rein JLS-basierten Arbeit ließen sich in diesem Vergleich etliche Bugs aufdecken. Einige Abweichungen ließen sich auf Fehler in ECJ zurückführen, aber in vielen Fällen hat der Spezifikationsautor bestätigt, dass ECJ korrekt und javac falsch umgesetzt war.

Idealerweise ließe sich sagen, dass mit Stichdatum 18. März alle Abweichungen analysiert und behoben wären. Leider entspricht das nicht der vollen Wahrheit.

Abweichungen auch nach dem Release

Einige Benutzer mussten bereits feststellen, dass ECJ und javac unter bestimmten Bedingungen unterschiedlich auf Default-Methoden reagieren. Zwar wurden diese genau dazu eingeführt, um bei einer Interface-Evolution die Kompatibilität zu bewahren. Aber wenn eine Bibliothek (insb. die Java Runtime Environment der Version 8) Default-Methoden enthält und diese Bibliothek in ein Java-7-Projekt eingebunden wird, ist das Verhalten des Java-7-Compilers unbestimmt, da er laut Spezifikation nur abstrakte Interface-Methoden kennt. Eine Abstimmung des Verhaltens beider Compiler in dieser Grauzone war vor dem Release leider nicht gelungen. Auch ist dringend von solch einer "halben Migration" zu JRE 8 ohne Anpassung des Compiler-Levels abzuraten. Dennoch wird das Release 4.4 von ECJ in diesem Bereich toleranter sein.

Schwieriger wird es aber in einem anderen Bereich: Für einen Java-8-Compiler ist Typechecking die komplexeste Aufgabe (mehr dazu gleich). Richtig unangenehm wird es, wenn nun ein Java-8-Programm sogenannte Raw Types benutzt, also wenn generische Klassen oder Interfaces ohne Typargumente verwendet werden, etwa "List" ohne spitze Klammern.

In diesem Bereich hat javac einen bekannten, weitreichenden Bug, der bewirkt, dass javac Programme akzeptiert, die laut JLS illegal sind. Bei einigen dieser Programmen könnte man noch sagen, dass sie zwar keine Java-Programme sind, aber trotzdem funktionieren. Bei anderen lässt sich jedoch zeigen, dass sie Laufzeitfehler erzeugen, insbesondere ClassCastException, und zwar an Stellen wo gar kein Cast im Quelltext steht. Das ist nicht das, was man von einer statisch typisierten Sprache erwartet. Leider hat Oracle mehrere Monate vor dem Release entschieden, diesen Bug für Java 8 nicht zu beheben.

Die ECJ-Implementierung scheint näher an der JLS zu sein als die von javac. Allerdings zeigt sich nun, dass Programme teilweise nur aufgrund von Compiler-Bugs akzeptiert wurden (JavaFX hat kürzlich bzgl. des Builder Pattern Ähnliches erfahren). Idealerweise würden nun beide Compiler, javac und ECJ, den Sprung auf Java 8 nutzen, um koordiniert die (teils alten) Compiler-Bugs zu beheben. Da Oracle nun davor zurückscheute, sahen sich die Eclipse-Entwickler teilweise genötigt, derlei Bugs absichtlich in die neue Implementierung zu injizieren. Da es sich aber um einen umfangreichen Bug handelt, dessen Ausmaß niemand wirklich genau beschreiben kann, wird diese Bug-Simulation immer nur eine Annäherung bleiben. Sobald man sich von der Spezifikation entfernt, ist identisches Verhalten der Compiler praktisch unmöglich.

Das ist eine unangenehme Situation für Benutzer von Java 8. Andererseits ist der Ausweg aus dieser Situation klar: Wer sich daran erinnert, wozu Raw Types überhaupt in Java 5 eingeführt wurden (um Migration älteren Codes zu erleichtern), fragt sich doch, ob diejenigen, die jetzt auf Java 8 umsteigen wollen, nicht langsam die Migration auf Java 5 abgeschlossen haben sollten.

Was bleibt unterm Strich? Jede Abweichung, die bei der Entwicklung der Alternativimplementierung aufgedeckt wird, kann die Gesamtqualität von Java nur verbessern, sofern die entdeckten Fehler auch behoben werden.