Java SE 11

Seit Kurzem gibt es die neue Version 11 von Java SE auf der Downloadseite von Oracle. 

Neben ein paar neuen Features, ist vor allem die geänderte Lizenzvereinbarung interessant. Während bislang Java immer (d.h. unabhängig von der Art des Einsatzes) kostenlos war, ändert das nun mit Java SE 11. 

Kommerzieller Einsatz von Java SE ist ab Version 11 mit der offiziellen Version von Java kostenpflichtig. Abhilfe schafft hier der Einsatz von OpenJDK, welches ebenfalls von Oracle verteilt wird. OpenJDK ist kompatibel und diverse Tests waren erfolgreich: Applikationen laufen unter OpenJDK 8 wie bisher unter Java SE 8.

Java SE 9 ist quasi nicht existent, da es zwischen Version 8 und 10 zwischen Stuhl und Bank gefallen ist. Für die Entwicklung kann es ignoriert werden. Für Neuentwicklungen (Stand Oktober 2018) sollte direkt mit Java 11 gestartet werden.

In Java SE 11 sind diverse Teile entfernt worden, die bislang standardmässig enthalten waren. Darunter fallen WebStart Support, JavaFX und verschiedene Tools (z.B. Mission Control). Ausserdem gibt es kein JRE mehr für Java SE 11. Um Java-Programme laufen zu lassen, kann entweder das SDK verwendet werden oder man baut sich sein JRE nach Bedarf “von Hand”.

Wie Oracle merkt, dass man ‘unerlaubterweise‘ die kommerzielle Version verwendet, weiss ich nicht. Evtl. telefoniert Java nach Hause, was bei internen Applikationen je nach Usecase mit Firewalls verhindert werden könnte. Dazu suche ich noch Infos und ergänze es dann hier.

Java SE 8 Programmer II: Varia

Hier ein paar Dinge, die prüfungsrelevant sind, aber sonst NIE passieren:

  • Wenn man einen Array mit einer negativen Grösse initialisiert, wird eine NegativeArraySizeException geworfen.
  • Bei einem switch() spielt die Reihenfolge der Optionen keine Rolle. Also kann default: mittendrin stehen.
  • Um Strings an einen StringBuffer anzuhängen, verwendet man append().
    Die Methode concat(...) von String hängt einen String an den anderen (analog '+')

Java SE 8 Programmer II: Locale

Für die Zertifikatsprüfung sind nur Sprache und Region (=Land) relevant.

Um Locale Objekte zu erzeugen, gibt es verschiedene Möglichkeiten:

  • new Locale().Builder mit set...() ... build()
  • new Locale(...)
    variable Parameter: language, country, variant
    Im resultierenden Locale Objekt ist das Land in Kleinbuchstaben und die Sprache in Grossbuchstaben, getrennt von einem '_'. Eine Prüfung auf Gültigkeit findet nicht bei Initialisierung sondern erst beim Gebrauch statt.
  • Locale::forLanguageTag(...)   z.B. "en-US". Die Ausgabe verwendet den '_'.

Ausserdem gibt es vordefinierte Locale Konstanten in Locale. Bei vordefinierten Ländern ist die jeweilige Standard-Sprache hinterlegt.

Sprachinformationen sind in Properties-Files (ResourceBundle) abgelegt, deren Name die Sprach- und Landes-Angaben enthalten kann.
Das Properties-File wird mit getBundle() gelesen, dabei wird der Filename (ohne Endung) verwendet, um die Default-Sprache zu erhalten.
Die Properties-Files enthalten Key-Value-Paare, die mit '=', ':' oder einem Leerzeichen getrennt sind (das ist konfigurierbar).

Es gibt zwei Subklassen von ResourceBundle:

  • PropertyResourceBundle
  • ListResourceBundle: die abstrakte Klasse enthält Locale-spezifische Objekt, die mit getContents() ausgelesen werden

Java SE 8 Programmer II: JDBC

Seit Java 8 müssen Driver nicht mehr manuell über den DriverManager geladen werden. Der Treiber initialisiert sich selbst und registriert sich mit dem DriverManager. Die Implementierung ist Hersteller-abhängig.
Wenn nun eine Connection zu einer DB per JDBC URL angefordert wird, liefert der DriverManager den passenden Treiber. Die JDBC URL ist herstellerspezifisch.
Wenn bei getConnection sowohl in der URL, wie auch in den Übergabe-Parametern Username und Passwort mitgegeben werden, ist es herstellerabhängig, mit welchen Werten authentifiziert wird.

Eine Connection ist per default auto-commit.

Ein Statement kann mehrfach ausgeführt werden. Das erzeugte ResultSet wird ungültig, sobald das Statement erneut ausgeführt wird.
PreparedStatements sind effizienter, da sie vom DBMS vorkompiliert werden. Dabei werden Parameter als '?' notiert.
Der Aufruf von execute(String) ist zwar für PreparedStatements zur Compile-Zeit korrekt (wegen Vererbung von Statement), aber zur Laufzeit wird eine SQLException ausgelöst.

Ein default ResultSet hat einen Cursor, der nur vorwärts bewegt werden kann. Am Anfang steht er vor der ersten Zeile.

Das Schliessen einer JDBC Objekts mittels .close() schliesst alle jeweils enthaltenen Komponenten. Der Aufruf von close() auf implizit geschlossenen JDBC Komponenten hat keinen Effekt.
Eine Connection ist AutoCloseable.

Java SE 8 Programmer II: Java NIO.2

Die Klasse java.nio.file.Path repräsentiert einen Pfad im Dateisystem, bestehend aus Root, einer variablen List von Verzeichnissen und einem fakultativen File-Namen. Dabei wird per se nicht sichergestellt, dass das referenzierte Element im File-System tatsächlich existiert. Für den Aufbau eines Pfads wird java.nio.file.Paths.get(...) verwendet. Die angebotene Methode .get(URI uri) wird sehr selten verwendet.

getParent() gibt den Pfad vom Anfang an ohne Root (e.g. '/') und ohne den File-Namen zurück. Wenn kein Pfad vorhanden ist, gibt diese Methode null zurück.
getRoot() gibt den Root oder null zurück.

java.nio.file.Files verfügt ausschliesslich über statische Methoden, die für Basistasks rund um Files benötigt werden. Dabei wird das unterliegende Filesystem so weit möglich abstrahiert. Symbolischen Links wird per Default gefolgt.
Files.readAllLines(...) lieferte eine List<String> zurück, während Files.readAllBytes(...) einen bytes[] liefert.
Mit .delete() können Files und leere Verzeichnisse gelöscht werden.
Beim Kopieren und Verschieben von Files, lässt sich mittels StandardCopyOptions das Verhalten steuern:

Bildschirmfoto 2018-01-29 um 07.58.51.png

Beim Kopieren von einem File zu einem anderen kann die ATOMIC_MOVE Option nicht verwendet werden. Beim Kopieren von einem File in einen OutputStream können keine Optionen gesetzt werden. Beim Kopieren von einem InputStream in ein File kann nur REPLACE_EXISTING verwendet werden.
Beim Verschieben können nur ATOMIC_MOVE und REPLACE_EXISTING verwendet werden. Wenn ATOMIC_MOVE gesetzt ist, wird REPLACE_EXISTING ignoriert (auch wenn es gesetzt wurde).

Neben den StandardCopyOptions existiert noch die LinkOption, die festlegt, ob Links nicht gefolgt werden soll.

Hinter dem Interface FileAttributeView sind die Filesystem-spezifischen Attribute (für read/update) zu finden. Die üblichen, allgemein verfügbaren File-Attribute sind in BasicFileAttributeView zusammengefasst.

Streams mit NIO.2
Um den File-Inhalt als Stream<String> zu erhalten, kann die Methode lines(...) verwendet werden bei der optional das Charset gesetzt werden kann (Standard: UTF-8).
Mittels list(Path) wird lazy ein Stream<Path> erzeugt, dabei wird nicht rekursiv operiert.
Mittels walk(...) wird lazy ein Stream<Path> erzeugt, der rekursiv operiert (depth-first). Per Default wird symbolischen Links nicht gefolgt, was sich per FileVisitOption ändern lässt.
Die find(...)-Methode arbeitet grundsätzlich wie walk(...) mit gesetzter Verschachtelungstiefe, aber hier gibt es die Möglichkeit direkt einen Filter in Form eines BiPredicate<Path, BasicFileAttributes> mitzugeben. Grundsätzlich könnte man das auch mit walk(...) und nachgelagerten Filtern lösen. Dies wäre aber deutlich weniger effizient.

Java SE 8 Programmer II: Threads

Im Gegensatz zu Runnable kann Callable einen Returnwert von definiertem Typ zurückgeben und eine Exception werfen.
Um die Erzeugung, den Start und das Ende von Runnable und Callable zu verwalten, kann ExecuterService zum Einsatz kommen. Ein ExecuterService wird von der Executors Factory erzeugt und kann einzelne Threads oder einen Thread-Pool verwalten.
Mittels ExecutorService können einzelne (via submit(...)) oder mehrere (via invokeXYZ(Collection<? extends Callable>) gestartet werden. Die von ExecutorService gestarteten Threads können per shutdown() (ordentliches Task-Ende; keine neuen Tasks) und shutdownNow() (sofortiges Ende alles Tasks; gibt eine Liste der pending Tasks zurück) wieder beendet werden.

Ein Objekt von Typ Future repräsentiert ein Resultat einer asynchronen Berechnung.
Bei der Verwendung von get() einer Future-Instanz wartet der aktuelle Thread auf die Antwort.

synchronized und Atomic Variables
Mit synchronized können Methoden und Code Blocks vor gleichzeitigem Zugriff geschützt werden.
Um Zugriffe auf Variablen mit Basisdatentypen zu schützen, können AtomicBoolean, AtomicInteger und AtomicLong verwendet werden. Für vor parallelem Zugriff geschützte Objektreferencen kann AtomicReference<T> verwendet werden.
Die Änderung des Inhalts kann u.. per set(...), getAndSet(...) und compareAndSet(...) vorgenommen werden. Wenn der Wert verändert wird, gibt es die Varianten addAndGet(...), getAndAdd(...), incrementAndGet(), getAndIncrement(), decrementAndGet() und getAndDecrement().
Ausserdem gibt es die Möglichkeit mit accumulateAndGet(), getAndAccumulate(), getAndUpdate() und updateAndGet() Atomic Variables in Lambdas zu verwenden.

CyclicBarrier
Damit lässt sich das Verhalten von join() elegant auf das leistungsfähigere Framework rund um  Threads abbilden. Wichtig ist, darauf zu achten, wieviele Threads bei der Initialisierung der CyclicBarrier angegeben werden.

CopyOnWriteArrayList
Ein Iterator läuft über den Datenstand des Arrays zum Zeitpunkt der Kreation des Iterators. Deshalb werden nie ConcurrentModificationExceptions geworfen. Die Methode remove() ist auf diesem Iterator nicht implementiert, was zu einer UnsupportedOperationException führt.
Alle Methoden von ArrayList funktionieren hier genau gleich. Neu sind addIfAbsent(...) und addAllAbsent(...).

Fork/Join Framework
Dies ist das Framework für Tasks (aka ForkJoinTask), die sich in kleinere Aufgaben aufgeteilt werden können, die dann parallel abgearbeitet werden können. Diese Tasks liegen in einem ForkJoinPool (Implementierung von ExecutorService Interface), der per Default mit der Anzahl der zur Verfügung stehenden Prozessoren skaliert wird.
Hier gibt es zwei Klassen RecursiveAction und RecursiveTask, die Runnable und Callable entsprechen. Beide haben die Methode compute().
Die compute() Methode ist folgendermassen aufgebaut:

Bildschirmfoto 2018-01-29 um 13.52.41.png

Die Tasks innerhalb von compute werden durch eine Variante von invoke() aufgerufen. Wobei die Variante invoke() synchron auf die Fertigstellung wartet.

Probleme bei Tasks
Beim Deadlock blockieren sich zwei Threads gegenseitig, während bei Starvation ein Thread eine Resource hält und nicht freigibt, die ein anderer Thread auch verwenden möchte.
Bei Livelock sind viele Threads damit beschäftigt Aktionen auszulösen, auf die sie gegenseitig hören und reagieren. Das Programm läuft quasi heiss. 
Bei Race conditions ändern verschiedene Threads gleichzeitig die gleichen Werte. Das Resultat lässt sich daher nicht vorhersagen.