Download wie immer unter https://www.eclipse.org/eclipseide/.
Was (mich) nervt, ist der zeitgleiche Release von neuen Java-Versionen und der Eclipse IDE. Daher kann die Eclipse-Version Java 15 nur über ein Plugin: https://marketplace.eclipse.org/content/java-15-support-eclipse-2020-09-417
Schon seit Java 1.0 gibt es den Quellcode der Standardbibliotheken (falls er beim JDK mitinstalliert wurde, befindet er sich im Wurzelverzeichnis unter dem Namen src.zip), und jeder Interessierte konnte einen Blick auf die Implementierung werfen. Zwar legte Sun damals also die Implementierungen offen, doch weder die Laufzeitumgebung noch der Compiler oder die Bibliotheken standen unter einer akzeptierten Open-Source-Lizenz. Zehn Jahre seit der ersten Freigabe von Java gab es Forderungen an Sun, die gesamte Java-Plattform unter eine bekanntere Lizenzform wie die GNU General Public License (GPL) oder die BSD-Lizenz zu stellen. Dabei deutete Jonathan Schwartz in San Francisco bei der JavaOne-Konferenz 2006 schon an: »It’s not a question of whether we’ll open source Java, now the question is how.« War die Frage also statt des »Ob« ein »Wie«, kündigte Rich Green bei der Eröffnungsrede der JavaOne-Konferenz im Mai 2007 die endgültige Freigabe von Java als OpenJDK (http://openjdk.java.net/) unter der Open-Source-Lizenz GPL 2 an. Dem war Ende 2006 die Freigabe des Compilers und der virtuellen Maschine vorausgegangen.
Obwohl OpenJDK unter der GPL stand, enthielt es doch Teile wie den Font-Renderer, Sound-Unterstützung, Farbmanagement oder SNMP-Code, die als binäre Pakete beigelegt wurden, weil etwa die Rechte zur Veröffentlichung fehlten. Sun nennt diese Teile, die etwa 4 % vom JDK 6 ausmachen, belasteten Code (engl. encumbered code). Das hinderte puristische Linux-Distributoren daran, OpenJDK auszuliefern. RedHat startete im Juni 2007 das Projekt IcedTea, um diese binären Teile auf der Basis des OpenJDK durch GPL-Software zu ersetzen. So basiert der Font-Renderer zum Beispiel auf FreeType und das Farbmanagement auf little CMS. Mit diesen Ersetzungen erfüllte das OpenJDK mit IcedTea im Juni 2008 die Anforderungen des Technology Compatibility Kit (TCK) von Sun und ist in der Öffentlichkeit seither unter dem Namen OpenJDK 6 bekannt. Daraufhin floss das OpenJDK 6 plus der Ersetzungen unter der GPLv2 in Linux-Distributionen wie Fedora und Debian ein.
Das OpenJDK bildet die Basis von Java 8, und jeder Entwickler kann sein eigenes Java zusammenstellen und beliebige Erweiterungen veröffentlichen. Damit ist der Schritt vollzogen, dass auch Java auf Linux-Distributionen Platz finden darf, die Java vorher aus Lizenzgründen nicht integrieren wollten.
Auch wenn es sich so anhört, als ob das Oracle JDK bzw. OpenJDK das Gleiche sei, ist das nicht ganz richtig: Zwar basieren Oracle JDK und OpenJDK auf den gleichen Quellen (bei der Version 7 etwa zu 95 %), doch sind beim Oracle JDK immer noch proprietäre Dinge enthalten, und nicht alles ist hundertprozentig quelloffen und GPL. Das gilt für die Version 7 wie für die Version 6. Das Oracle JDK steht unter der Binary Code License; genau die muss jeder abnicken, der das JDK von der Webseite laden möchte.
Bei der 6er-Reihe kommt noch eine Besonderheit dazu, wie es die Versionsnummern ganz gut zeigen. Während das Oracle JDK zum Beispiel im Juni 2011 bei Versionsnummer 1.6.0_26-b03 steht, ist das OpenJDK bei Version 6 b22. Die Versionsnummern sind deshalb völlig unabhängig, weil beide Projekte auch unabhängig voneinander laufen. Das hat mit der Geschichte zu tun. Nach der Entwicklung des JDK 6, das nicht unter der GPL steht, ging es mit dem JDK 7 logisch weiter. Aus dem JDK 7 (Build 10) entstand dann OpenJDK, das heute mit der Versionsnummer OpenJDK 7 genannt wird. OpenJDK 7 und JDK 7 entwickeln sich Hand in Hand, und Code-Änderungen gehen mal in die eine Richtung und mal in die andere.
Jetzt kommt die Besonderheit: Das OpenJDK 6 entstand nicht, wie vermutet werden könnte, aus dem Oracle JDK 1.6, sondern aus dem OpenJDK 7 (Build 20). Es wurden nur Java 7-Eigenschaften entfernt: Die meisten Patches am OpenJDK 6 sind Backports von OpenJDK 7. Änderungen am OpenJDK 7 stammen überwiegend von Oracle, und häufig ist es die Firma Red Hat, die diese Änderungen in OpenJDK 6 portiert. Zwischen dem OpenJDK 6 und dem JDK 1.6 gibt es einen Quellcodeaustausch bei Bug-Fixes, doch die Codebasis ist unterschiedlich. Oracle JDK 6 ist im Wartungsmodus, und großartige Veränderungen passieren bis auf Fehlerbereinigungen nicht.
Oracle JDK 8 ist die Version, die die Download-Seite von Oracle anbietet; das OpenJDK 8 liegt auf einem eigenen Server http://openjdk.java.net/projects/jdk8/. Das OpenJDK bildet die Referenzimplementierung für Java SE, nicht das Oracle JDK.
OpenJDK
Das freie und unter der GPL stehende OpenJDK (http://openjdk.java.net/) bildet nunmehr die Referenzimplementierung für Java SE. Alle Entwicklungen finden dort statt. Der Fortschritt ist live zu beobachten, regelmäßig fixen und erweitern Hunderte von Entwicklern die Codebasis. Die Quellen für das OpenJDK lassen sich im Mercurial-Repository unter http://hg.openjdk.java.net/jdk/jdk14 einsehen (ein Wechsel auf GitHub wird diskutiert). Viele Technologien, die Oracle vorher nur im Oracle JDK hatte, wurden in das OpenJDK übertragen, etwa Java Flight Recorder, Java Mission Control, Application Class-Data Sharing und ZGC (Zero-Garbage-Collector).
OpenJDK-Builds von Oracle
Oracle selbst compiliert das OpenJDK und bietet es an. Aktuelle Versionen sind über http://jdk.java.net/ verlinkt. Es gibt von Oracle OpenJDK-x64-Builds für Windows, Linux und macOS.
Das Oracle OpenJDK steht unter der GNU General Public License v2 mit der Classpath Exception (GPLv2+CPE). Oracle selbst hat angekündigt, bei neuen Versionen keine Updates mehr für die alten Versionen zu veröffentlichen.
OpenJDK-Builds von AdoptOpenJDK
AdoptOpenJDK (https://adoptopenjdk.net/) ist eine Stiftung und betreibt eine Serverfarm, die regelmäßig Builds vom OpenJDK baut und dazu weitere Software wie die JavaFX-Implementierung OpenJFX und alternative Laufzeitumgebungen wie Eclipse OpenJ9 einbindet. Es wird angekündigt, auch ältere Versionen mit Bugfixes zu versorgen. Zu den Unterstützern zählen Amazon, IBM/Red Hat, Microsoft, Pivotal und viele weitere.
Von der Webseite lässt sich für unterschiedliche Betriebssysteme eine Version herunterladen. Angeboten werden Builds u.a. für: Windows, Linux, macOS.
AdoptOpenJDK zählt aktuell zu den populärsten OpenJDK-Distributionen.
Weitere OpenJDK-Builds
Das Unternehmen Azul bietet Builds unter dem Namen Zulu an, auch lässt sich ein Support-Vertrag abschließen: http://www.azul.com/downloads/zulu/. Neben den Plattformen Windows, Linux und macOS gibt es von Azul ebenfalls Docker-Images.
Red Hat bietet neben Linux auch eine Windows-Version vom OpenJDK an: http://developers.redhat.com/products/openjdk/overview/. Die Integration in Linux ist sehr gut, und Red Hat pflegt auch noch Sicherheitsupdates in Java 6 und Java 7 ein.
SAP bietet mit der SapMachine (http://sap.github.io/SapMachine/) Builds für diverse Betriebssysteme und auch ein Docker-Image.
Amazon Corretto (http://aws.amazon.com/de/corretto/) wird intern von Amazon in der Cloud eingesetzt und ist für die LTS-Versionen Java 8 und Java 11 kostenlos.
Alibaba Dragonwell (http://github.com/alibaba/dragonwell8) ist eine vom OpenJDK 8 abgeleitete Implementierung von Alibaba.
Von BellSoft gibt es das Liberica JDK (http://bell-sw.com/) für die Plattformen Windows x86_64 und auch noch Windows x86, macOS x86_64, Linux x86_64, Linux ARMv8, Linux ARMv7 HardFloat, Solaris SPARC und Solaris x86_64.
Apple pflegte lange Zeit eine komplett eigene JVM, bis Apple den Code an Oracle für das OpenJDK übergab. Auch Google setzt bei Android neuerdings auf das OpenJDK.
Oracle JDK
Oracle vermarktet auf der Basis des OpenJDK sein eigenes Projekt, Oracle JDK. Ab Java 11 sind das Oracle JDK und OpenJDK vom Code her (nahezu) identisch. Das Oracle JDK ist die »offizielle« Version, die die Java-Download-Seite von Oracle anbietet. Wenige kleine Unterschiede sind die Paketierung (das Oracle JDK hat einen Installer, das Oracle OpenJDK ist nur ein ZIP), die Versionskennung und ein paar weitere Module.[1]
Long Term Support (LTS)
Die halbjährlichen Java-Releases haben zur Folge, dass Versionen immer dann veraltet sind, wenn eine neue Version erscheint. In dem Moment, in dem Java 10 kam, war Java 9 veraltet; das Gleiche gilt bei Java 14 – es machte sofort Java 13 zur alten Version. Das allein wäre kein Problem, wenn die älteren Versionen mit Sicherheitsupdates versorgt würden. Aber Oracle investiert für die Allgemeinheit keine Zeit und Mühe mehr und pflegt die alten Versionen nicht.
Für Unternehmen ist das ein Problem, denn es erzeugt Stress, mit den Änderungen mitziehen zu müssen. Aus diesem Grund bietet Oracle alle drei Jahre eine Java-Version mit Long Term Support (LTS) und versorgt sie mit Updates und Sicherheitspatches. Die LTS-Versionen nach Java 8 sind Java 11 (September 2018) und dann nach 3 Jahren Java 17 (September 2021). Das ist für weniger agile Unternehmen gut. Oracle will seine Java SE 8-Implementierung noch viele Jahre pflegen.
Kommerzialisierung des Oracle JDK
Auf den ersten Blick sieht das gut aus: Es gibt regelmäßige Updates für agile Unternehmen, und die konservativen Unternehmen setzen auf eine LTS-Version. Das Problem ist allerdings, dass alle Oracle JDK-Versionen nicht kommerziell eingesetzt werden dürfen; der Hersteller erlaubt die Nutzung nur für »development, testing, prototyping or demonstrating purposes«.
Für Java 8 endete die Schonfrist im Januar 2019. Das dürfte vielen Entwicklern gar nicht bewusst sein, denn seit 20 Jahren sind Unternehmen daran gewöhnt, das Oracle JDK für alles einzusetzen. Und wir wissen alle, wie viele Menschen wirklich die Lizenzbedingungen lesen …
Wer also das Oracle JDK kommerziell einsetzen möchte und nicht nur in einer Entwicklungs- oder Testumgebung, muss eine Lizenz von Oracle erwerben. Es wird monatlich abgerechnet, die Vertragslaufzeit beträgt mindestens ein Jahr. Es stehen zwei Modelle zur Auswahl:
Java SE Subscription
Java SE Desktop Subscription
Für Serveranwendungen
Für Client-Anwendungen
Abrechnung pro Prozessor
Abrechnung pro Benutzer
Bis 25 USD/Monat, für 1–99 Benutzer
Bis 2,50 USD/Monat für 1–999 Benutzer/Clients
Zwei Lizenzmodelle für Oracle Java SE
Oracle wendet bei der Java SE Subscription das gleiche Geschäftsmodell wie bei der Oracle-Datenbank an. Wie genau ein Rechner in der Cloud mit einer unbestimmten Anzahl der Prozessoren abgerechnet werden soll, ist noch unklar.[2] Interessenten sollten die »Oracle Java SE Subscription FAQ« unter http://www.oracle.com/technetwork/java/javaseproducts/overview/javasesubscriptionfaq-4891443.html studieren und Oracle-Berater hinzuziehen. Wer Client- und Serveranwendungen nutzt, muss zweimal bezahlen. Statt »write once, run anywhere« heißt es nun »write once, pay everywhere«.
Die Kosten können sich schnell summieren, doch bekommen Unternehmen damit Support und insbesondere für Java 8 immer noch für einige Jahre Unterstützung. Der Nachteil ist, dass es das Subscription-Modell nur für die LTS-Versionen gibt, Unternehmen also gezwungen werden, größere Versionssprünge zu machen. Nach Java 11 kommt erst im September 2021 die Version Java 17 mit dem nächsten LTS.
Sind Variablen final, heißt das lediglich, dass es eine einmalige Zuweisung geben darf. Ob die Werte allerdings zur Laufzeit berechnet werden oder nicht, hat erst einmal nichts mit final zu tun. In folgendem Beispiel ist die Variable eine zur Compilezeit bekannte Konstante:
public class Finance { public static final int TAX = 19; }
Greift eine andere Klasse auf die Variable TAX zu, ist das im Quellcode nicht als direkter Variablenzugriff Finance.TAX kodiert, sondern der Compiler hat das Literal 19 direkt an jeder Aufrufstelle eingesetzt. Dies ist eine Optimierung des Compilers, die er laut Java-Spezifikation vornehmen kann.
Wir sprechen in diesem Zusammenhang von einer compile-time constant expression wenn gilt:
ein Attribut ist final,
der Datentyp ist ein primitiver oder String,
das Attribut wird mit einer vom Compiler berechneten Konstanten initialisiert.
Das Einsetzen der konstanten Werte ist praktisch, bringt aber ein Probleme mit sich, wenn das finale Attribut sich ändert. Dann muss nämlich auch jede Klasse übersetzt werden, die Bezug auf die Konstante hatte. Werden die abhängigen Klassen nicht neu übersetzt, ist in ihnen immer noch der alte Wert eincompiliert.
Die Lösung ist, die bezugnehmenden Klassen neu zu übersetzen und sich am besten anzugewöhnen, bei einer Änderung einer Konstanten gleich alles neu zu compilieren. Ein anderer Weg transformiert die finale Variable in eine später initialisierte Form:
public class Finance { public static final int TAX = Integer.valueOf( 19 ); }
Die Initialisierung findet im statischen Initialisierer statt, und die Konstante mit dem Literal 19 ist zunächst einmal verschwunden. Der Compiler wird also beim Zugriff auf Finance.TAX keine Konstante 19 vorfinden und daher das Literal an den Aufrufstellen nicht einbauen können. In der Klassendatei wird der Bezug Finance.TAX vorhanden sein, und eine Änderung der Konstanten erzwingt keine neue Übersetzung der Klassen.
This is the only breaking change in this release, and it affects only users of the guava-gwt artifact, not people who use only the guava artifact. This release contains no changes that break binary compatibility for any users.
API documentation for Guava classes is now easier to reach. For example, for ImmutableList, visit guava.dev/ImmutableList. Also, more easily access the index at guava.dev/api.
collect: Annotated FluentIterable.from(FluentIterable) with @DoNotCall. (b1c77b7)
collect: Made ceiling, floor, headSet(E, boolean), and tailSet(E, boolean) methods available in the GWT-emulated ImmutableSortedSet. (7e0fe90, 5f2fbf2)
graph: Made it possible to set a stable incident edge order by calling the newly added method [Value]Graph.Builder.incidentEdgeOrder(ElementOrder.stable()). (7016402)
graph: Added incidentEdgeOrder() to the [Value]Graph interfaces. (cde576e)
util.concurrent: Added Duration-based default methods to ListeningScheduledExecutorService. (931e83f)
util.concurrent: Removed @Beta from Service and related classes. (dc46627)
util.concurrent: Deprecated the 1-arg overload of ServiceManager.addListener. (86e3620)
util.concurrent: Changed the return type of ServiceManager.servicesByState() to ImmutableSetMultimap (but also retained a method with the old signature for binary compatibility). (31999ae)
util.concurrent: Made it safe to load the AbstractFuture class from a ForkJoinPool thread under a security manager. (6e0c5b5)
Die Aufgaben gibt es unter http://tutego.de/javabuch/aufgaben/ — viele Aufgaben sind mit Lösungsvorschlägen. Neue Lösungen nehme ich gerne entgegen und können auch gerne unter den Kommentaren gepostet werden.
Warten wir nun auf Java 15 und darauf, dass es weiter geht und nicht alles nur ein Preview bleibt. Und hoffen wir, dass viel mehr auf Java 11 aufwärts setzen und wir endlich von Java 8 wegkommen.
Java hat seit Version 1.0 eine switch-Anweisung zum Kontrollfluss. Im Wesentlichen basiert die Syntax auf der Programmiersprache C, die auf die 1970er Jahre zurückgeht. In Java 12 wurde eine neue Syntax probeweise eingeführt, in Java 13 verändert und in Java 14 endgültig integriert.
Insgesamt kann switch in vier Formen auftauchen:
Anweisung/Ausdruck
Ab Java-Version
Syntax
Durchfall
vollständige Abdeckung
Anweisung
1.0
:
Ja
Nein
Anweisung
14
->
Nein
Nein
Ausdruck
14
:
Ja
Ja
Ausdruck
14
->
Nein
Ja
Vier Typen von switch
Den ersten Typ haben wir schon ausgiebig betrachtet, schauen wir uns die weiteren Varianten an.
Vereinfachte Switch-Anweisung, kein Durchfall, keine vollständige Abdeckung
Bei der vereinfachten switch-Anweisung steht hinter dem Label bzw. default kein Doppelpunkt, sondern ein ->. Dieser hat nichts mit Lambda-Ausdrucken zu tun, auch wenn die Symbole gleich sind. Hinter dem Pfeil steht entweder ein Ausdruck, ein Block in geschweiften Klammern oder ein throw-Anweisung, die eine Ausnahme auslöst. Implizit beendet ein break jeden Zweig, es gibt also kein Durchfallen mehr.
Beispiel
String operator = "+";
switch ( operator ) {
case "+" -> System.out.println( "Plus" );
case "-" -> { String minus = "Minus"; System.out.println( minus ); }
}
Dadurch, dass bei mehreren Anweisungen immer Blöcke gesetzt werden müssen, tritt eine lokale Variable auch nicht aus dem Bereich aus.
Ein default kann gesetzt werden, muss aber nicht. Das switch muss nicht jede Möglichkeit abdecken, was bei Zahlen und Strings eh nicht funktioniert.
Switch-Ausdrücke, kein Durchfall, vollständige Abdeckung
Traditionell finden sich die Fallunterscheidungen mit switch als Anweisung und Anweisungen geben nichts zurück. In Java 14 ist es möglich, switch als Ausdruck mit Ergebnis zu nutzen.
Ausdrücke müssen immer Ergebnisse liefern, und folglich muss switch immer einen Pfad auf einen Wert nehmen. Der übliche Fall ist default wie gezeigt, es gibt allerdings Sonderfälle, wie bei Aufzählungen, wo der Compiler prüfen kann, dass alle Möglichkeiten abgedeckt sind.
Falls rechts neben dem Pfeil kein einfacher Ausdruck steht, sondern ein Block muss auch dieser Block ein Ergebnis zurückgeben. Dafür wird das neue Schlüsselwort yield eingesetzt, hinter dem ein Ausdruck kommt.
Beispiel
String operator = "+";
System.out.println( switch ( operator ) {
case "+" -> "Plus";
case "-" -> { String minus = "Minus"; yield minus; }
default -> throw new IllegalArgumentException( "Unknown operator" );
} );
Ein Block muss ein yield besitzen oder eine ungeprüfte Ausnahme auslösen.
Switch-Expression mit :-Syntax, mit Durchfall, vollständige Abdeckung
Auch die Doppelpunkt-Syntax lässt sich als Ausdruck einsetzten, mit ihr ist auch ein Durchfall wieder möglich; ein yield ist zwingend, oder eine ungeprüfte Ausnahme. Die Syntax birgt mit dem Durchfallen eine Fehlerquelle, sodass es vielleicht die schlechteste Variante ist.
Beispiel
String operator = "+";
System.out.println( switch ( operator ) {
case "+" : yield "Plus";
case "*" : System.out.println( "Sternchen" );
case "×" : yield "Mal";
default : throw new IllegalArgumentException( "Unknown operator" );
} );
Groovy ist eine Programmiersprache auf der JVM, die bald schon 20 Jahre in der Entwicklung ist. In den letzten Jahren ist es leise um die Sprache geworden und man bekommt den Eindruck, dass Groovy zur Konfigurationssprache für Gradle-Builds degradiert ist.
Nun ist Groovy 3 erschienen mit einem ganz neuen Parser, Parrot. Die Anzahl der Neuerungen ist vielfältig, weil man Groovy nun als echte Erweiterung von Java aufgebaut hat. An vielen kleinen Stellen gab es vorher Fehler, Lambda-Ausdrücke waren anders, Array-Initialisierungen waren mit {} nicht möglich, und vieles mehr.
Gute objektorientiert entworfene Systeme zeichnen sich dadurch aus, dass es eine hohe Interaktion mit anderen Objekten gibt. Idealerweise zerlegt eine Klasse ein Problem nur bis zu dem Punkt, an dem es sich einer anderen Klasse bedienen kann, die dieses einfachere Problem löst. Schwierig wird es, wenn eine eigene Klasse auf eine andere komplexe Klasse zurückgreift und das Objekt nur dann sinnvoll arbeitet, wenn das referenzierte Objekt da ist und irgendwie sinnvoll antwortet. Diese Abhängigkeit ist ungünstig, denn das Ziel eines guten Tests besteht ja darin, lokal zu sein, also die eigentliche Klasse zu testen und nicht alle referenzierten Klassen um sie herum gleich mit.
In der Praxis begegnen uns drei Hilfskonstrukte, die die Lokalität von Tests ermöglichen:
Fake-Objekte: Sie sind eine gültige Implementierung einer Schnittstelle. Wenn zum Beispiel ein Repository auf die Datenbank geht, kann ein Fake-Implementierung Datensätze in einer Datenstruktur speichern. Das Verhalten ist nachgebildet und vereinfacht, aber funktionsfähig. So liefert ein Fake-Repository statt Kunden aus der Datenbank immer die gleichen N vorgefertigten Kunden. Fake-Objekte sind auch praktisch, wenn zum Beispiel eine GUI-Anwendung programmiert wird, die statt echter Datenbankdaten erst einmal mit den Fake-Objekten entwickelt wird und so die Demodaten anzeigt. Wenn ein Team die GUI baut und ein anderes Team den Service, so können beide Gruppen unabhängig arbeiten, und das GUI-Team muss nicht erst auf die Implementierung warten.
Stub-Objekte: Stub-Objekte implementieren ein bestimmtes Protokoll, sodass sie für den Testfall immer die gleichen Antworten geben können. Wenn etwa ein E-Mail-Service eine Methode isTransmitted() anbietet, so kann der Stub immer true liefern. Stubs haben also kein Verhalten, sondern der Rumpf der Methoden ist quasi leer und minimal. Sie gibt es nur für die Testfälle.
Mock-Objekte: Mock-Objekte werden von einem Testfall »aufgeladen« und zeigen dann das gewünschte Verhalten – sie liefern also nicht wie Stubs immer das gleiche Ergebnis. In der Regel werden Mock-Objekte durch Bibliotheken wie mockito(http://mockito.org) oder EasyMock (http://easymock.org) automatisch zur Laufzeit erzeugt.
Diese drei Typen können wir unter dem Oberbegriff Dummy-Objekt zusammenfassen. Grundsätzlich gilt bei den vier Begriffen aber, dass sie von Autoren nicht einheitlich verwendet werden.[1]
Beispiel: Mockito-Beispiel
Nehmen wir an, alles aus org.mockito.Mockito.* ist statisch importiert und wir wollen eine java.util.List aufbauen. Dazu muss Mockito erst etwas aufbauen, was sich wie List verhält:
List<?> mockedList = mock( List.class );
Im nächsten Schritt muss das Verhalten der speziellen Liste bestimmt werden:
package com.tutego.jaxb;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import java.io.PrintWriter;
public class App {
public static void main( String[] args ) throws Exception {
Dog dog = new Dog();
dog.name = "Wüffi";
Flea flea = new Flea();
flea.name = "<><> Böser Floh <><>";
dog.flea = flea;
JAXBContext jaxbContext = JAXBContext.newInstance( dog.getClass() );
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, true );
jaxbMarshaller.marshal( dog, new CDATAContentHandler( new PrintWriter( System.out ) ) );
}
}
Dog and Flea:
package com.tutego.jaxb;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class Dog {
public String name;
public Flea flea;
}
class Flea {
public String name;
}
CDATAContentHandler:
package com.tutego.jaxb;
import com.sun.xml.txw2.output.CharacterEscapeHandler;
import com.sun.xml.txw2.output.DataWriter;
import org.xml.sax.SAXException;
import java.io.IOException;
import java.io.Writer;
public class CDATAContentHandler extends DataWriter {
public CDATAContentHandler( Writer writer ) {
super( writer, "UTF-8", MinimumEscapeHandler.theInstance );
}
@Override
public void characters( char[] ch, int start, int length ) throws SAXException {
boolean useCData = false;
loop:
for ( int i = start; i < start + length; i++ )
switch ( ch[ i ] ) {
case '<': case '>': case '&': useCData = true;
break loop;
}
if ( useCData ) super.startCDATA();
super.characters( ch, start, length );
if ( useCData ) super.endCDATA();
}
}
/**
* Performs no character escaping. Usable only when the output encoding
* is UTF, but this handler gives the maximum performance.
*
* @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
*/
class MinimumEscapeHandler implements CharacterEscapeHandler {
private MinimumEscapeHandler() {
} // no instanciation please
public static final CharacterEscapeHandler theInstance = new MinimumEscapeHandler();
public void escape( char[] ch, int start, int length, boolean isAttVal, Writer out )
throws IOException {
// avoid calling the Writerwrite method too much by assuming
// that the escaping occurs rarely.
// profiling revealed that this is faster than the naive code.
int limit = start + length;
for ( int i = start; i < limit; i++ ) {
char c = ch[ i ];
if ( c == '&' || c == '<' || c == '>' || c == '\r' || (c == '\n' && isAttVal) || (c == '\"' && isAttVal) ) {
if ( i != start )
out.write( ch, start, i - start );
start = i + 1;
switch ( ch[ i ] ) {
case '&':
out.write( "&" );
break;
case '<':
out.write( "<" );
break;
case '>':
out.write( ">" );
break;
case '\"':
out.write( """ );
break;
case '\n':
case '\r':
out.write( "&#" );
out.write( Integer.toString( c ) );
out.write( ';' );
break;
default:
throw new IllegalArgumentException( "Cannot escape: '" + c + "'" );
}
}
}
if ( start != limit )
out.write( ch, start, limit - start );
}
}
Das „Kubernetes Native Java stack tailored for OpenJDK HotSpot and GraalVM, crafted from the best of breed Java libraries and standards.“ ist in der ersten Version erschienen und positioniert sich gegen Spring Boot und Jakarata EE.
Die append(…)-Methoden bei StringBuilder liefern die this-Referenz, sodass sich Folgendes schreiben lässt:
StringBuilder sb = new StringBuilder();
sb.append( "Android oder iPhone" ).append( '?' );
Jedes append(…) liefert das StringBuilder-Objekt, auf dem es aufgerufen wird – wir können also Methoden kaskadiert anhängen oder es bleiben lassen.
Wir wollen diese Möglichkeit bei einem Zauberer (Klasse Wizard) programmieren, sodass die Methoden name(String) und age(int) Spielername und Alter zuweisen. Beide Methoden liefern ihr eigenes Wizard-Objekt über die this-Referenz zurück:
class Wizard {
String name = "";
int age;
Wizard name( String name ) { this.name = name; return this; }
String name() { return name; }
Wizard age( int item ) { this.age = item; return this; }
int age() { return age; }
String format() {
return name + " ist " + age;
}
}
Erzeugen wir einen Wizard, und kaskadieren wir einige Methoden:
Der Ausdruck new Wizard() liefert eine Referenz, die wir sofort für den Methodenaufruf nutzen. Da name(String) wiederum eine Objektreferenz vom Typ Wizard liefert, ist dahinter direkt .age(int) möglich. Die Verschachtelung von name(„Gunalf“).age(60) bewirkt, dass Name und Alter gesetzt werden und der jeweils nächste Methodenaufruf in der Kette über this eine Referenz auf dasselbe Objekt, aber mit verändertem internem Zustand bekommt.
Beispiele dieser Bauart sind in der Java-Bibliothek an einigen Stellen zu finden. Sie werden auch Builder genannt.
Hinweis: Die Methode Wizard name(String) ist mit ihrer Rückgabe praktisch, verstößt aber aus zwei Gründen gegen die JavaBeans-Konvention: Setter dürfen keine Rückgabe haben und müssen immer mit set beginnen. JavaBeans sind also nicht so dieser kompakten Builder-Schreibweise „kompatibel“.