
Mal sehen wie es jetzt weitergeht.

Mal sehen wie es jetzt weitergeht.
RedHat hat mit „The critical missing pieces and a path forward“ (http://mail.openjdk.java.net/pipermail/jpms-spec-observers/2017-May/000874.html) ausgedrückt, Jigsaw so nicht unterstützen zu können.
Tim Ellison von IBM legt noch einmal nach in http://mail.openjdk.java.net/pipermail/jpms-spec-observers/2017-May/000870.html und adressiert mit Links weitere Probleme.
Insgesamt sehe ich drei Ausgänge:
Was meint ihr?
Schöne Tabelle, mit den Wechseln von X nach Y: https://erikbern.com/2017/03/15/the-eigenvector-of-why-we-moved-from-language-x-to-language-y.html.

XY sind Programmiersprachen, Web-Frameworks und Datenbanken.
Im JDK 9 ist ein neues Programm eingezogen: die JShell. Mit ihr lassen sich auf einfache Weise kleine Java-Programme und einzelne Anweisungen testen, sogenannte Snippets, ohne eine große IDE starten zu müssen. Die JShell ist eine Befehlszeile (Shell), die nach dem Read-Evaluate-Print-Loop-Prinzip arbeitet:
Das bekannteste Beispiel für eine REPL-Umgebung ist die Unix-Shell. Doch viele Skriptsprachen wie Lisp, Python, Ruby, Groovy und Clojure bieten solche REPL-Shells. Nun auch Java seit Version 9. Die Rückmeldung ist schnell, und gut zum Lernen und Ausprobieren von APIs.
Im bin-Verzeichnis vom JDK finden wir das Programm jshell. Rufen wir sie auf:
| Welcome to JShell -- Version 9-ea | For an introduction type: /help intro jshell>
Die JShell besitzt eingebaute Kommandos, die mit / beginnen, um zum Beispiel alle deklarierten Variablen ausgeben oder das Skript speichern. /help gibt eine Hilfe über alle Kommandos, /exit beendet JShell.
Nach dem Start wartet JShell auf die Snippets. Gültig sind:
Es sind also Teilmengen der Java-Sprache und keine eigene Sprache.
In der JShell lässt sich jeder Code schreiben, der im Rumpf einer Methode gültig ist. Ein Semikolon am Ende einer Anweisung ist nicht nötig:
jshell> System.out.println( "Hallo Welt" ) "Hallo Welt" Compilerfehler zeigt die JShell sofort an: jshell> System.out.pri() | Error: | cannot find symbol | symbol: method pri() | System.out.pri() | ^------------^
Ausnahmen müssen nicht behandelt werden, es lassen sich alle Methoden ohne try-catch aufrufen; falls es zu Ausnahmen kommt werden diese direkt gemeldet:
jshell> Files.exists( Paths.get("c:/") )
$2 ==> true
jshell> Files.exists( Paths.get("lala:/") )
| java.nio.file.InvalidPathException thrown: Illegal char <:> at index 4: lala:/
| at WindowsPathParser.normalize (WindowsPathParser.java:182)
…
| at (#3:1)
Die letzte Zeile zeigt die Zeilennummer im Skript an. Eine Liste der bisher eingegebenen Zeilen listet /list auf, und das inklusive Zeilennummern. Diese sind nützlich, wenn es zu Ausnahmen wie oben kommt.
jshell> /list
1 : System.out.println( "Hallo Welt" );
2 : Files.exists(Paths.get("c:/"))
3 : Files.exists(Paths.get("lala:/"))
Die JShell pflegt eine Historie der letzten Kommandos, die sich mit den Cursor-Tasten abrufen lässt. Es funktioniert auch die Vervollständigung mit der Tabulator-Taste wie in einer IDE, wobei die Groß-Kleinschreibung relevant ist:
jshell> Sys↹ System SystemColor SystemTray jshell> System.out.println(java.time.LocalDateTime.n↹ jshell> System.out.println(java.time.LocalDateTime.now( now( jshell> System.out.println(java.time.LocalDateTime.now()) 2017-03-23T11:50:43.859385900
Mit dem Cursor lässt sich in die Zeile vorher gehen und die Zeile nacheditieren.
Variablen lassen sich deklarieren und später jederzeit verwenden:
jshell> String name = "Christian" name ==> "Christian"
Die JShell gibt die Variable mit der Belegung zur Kontrolle aus.
Variablen lassen sich mit einem ganz neuen Typ redefinieren:
jshell> StringBuilder name = new StringBuilder( "Christian" ) name ==> Christian
Es lassen sich auch ohne Zuweisung Ausdrücke in die JShell setzen. Das Ergebnis des Ausdrucks wird einer temporären Variablen zugewiesen, die standardmäßig mit einem Dollar beginnt und der einer Zahl folgt, etwa $1. Auf diese Variable lässt sich später zugreifen:
jshell> BigInteger.TEN.pow(10) $1 ==> 10000000000 jshell> $1 $1 ==> 10000000000 jshell> $1.bitLength() $2 ==> 34 jshell> System.out.println(2*$2) 68
Welche Variablen in welcher Reihenfolge in der Sitzung deklariert wurden zeigt das Kommando /vars auf:
jshell> /vars | StringBuilder name = Christian | BigInteger $1 = 10000000000 | int $2 = 34
Wenn die JShell auf einen nicht kompletten Code trifft, symbolisiert die Ausgabe …> die Notwendigkeit einer weitere Eingabe:
jshell> System.out.println( ...> "Hallo" ...> + ...> " Welt" ...> ) Hallo Welt
Standardmäßig sind für den Java-Compiler alle Typen vom Paket java.lang direkt importiert. Die JShell erweitert das um eine ganze Reihe weiterer Typen. Wir können sie mit dem Kommando /imports erfragen:
jshell> /imports | import java.io.* | import java.math.* | import java.net.* | import java.nio.file.* | import java.util.* | import java.util.concurrent.* | import java.util.function.* | import java.util.prefs.* | import java.util.regex.* | import java.util.stream.* jshell> import java.awt.* jshell> /imports | import java.io.* | import java.math.* | import java.net.* | import java.nio.file.* | import java.util.* | import java.util.concurrent.* | import java.util.function.* | import java.util.prefs.* | import java.util.regex.* | import java.util.stream.* | import java.awt.*
Methoden und Klassen lassen sich deklarieren und auch wieder überschreiben, wenn eine neue Version eine alte ersetzen soll. JShell schreibt dann „modified“ bzw. „replaced“.
jshell> String greet(String name) { return "BÖLK " + name; }
| created method greet(String)
jshell> String greet(String name) { return "Mit vorzüglicher Hochachtung " + name; }
| modified method greet(String)
jshell> class MyFrame extends java.awt.Frame {}
| created class MyFrame
jshell> class MyFrame extends java.awt.Frame { MyFrame() { setTitle("FENSTER"); } }
| replaced class MyFrame
jshell> new MyFrame().show()
Welche Methoden und neue Typen in der Sitzung deklariert sind listet /methods und /types auf:
jshell> /methods | String greet(String) jshell> /types | class OkButton | class MyFrame
Exceptions müssen wie üblich behandelt werden, eine Sonderbehandlung, wie bei der direkten, in die JShell eingegeben Anweisungen, gibt es nicht.
Greift eine Methoden- oder Klassendeklaration auf Typen und Methoden zurück, die in dem Kontext noch nicht vorhanden sind, ist das in Ordnung; allerdings müssen alle Typen und Methoden spätestens dann bekannt sein, wenn der Code ausgeführt werden soll.
jshell> double cubic(double v) { return sqr(v) * v; }
| created method cubic(double), however, it cannot be invoked until method sqr(double) is declared
jshell> cubic(100)
| attempted to call method cubic(double) which cannot be invoked until method sqr(double) is declared
jshell> double sqr(double v) { return v*v; }
| created method sqr(double)
jshell> cubic(100)
$14 ==> 1000000.0
Snippets können in der JShell mit /save Dateiname gespeichert, mit /open Dateiname geöffnet und mit /edit in einem Standard-Editor bearbeitet werden.
Auf der Kommandozeile werden JShell-Skripte auf vorhandenen Skripten einfach ausgeführt mit:
$ jshell datei
Anders als die Benutzung von JavaScript aus Java heraus integriert sich die JShell nicht als Skript-Sprache. Stattdessen gibt es eine eigene API, in der die Klasse JShell im Mittelpunkt steht, wobei sich die Möglichkeiten der JShell-Kommandozeile eins zu eins in der API – dokumentiert unter http://download.java.net/java/jdk9/docs/jdk/api/jshell/overview-summary.html – wiederfinden lassen.
Ein einfaches Beispiel:
try ( JShell shell = JShell.create() ) {
// Semikolon wichtig!
String program = "java.math.BigInteger.TEN.pow( 10 );";
List<SnippetEvent> events = shell.eval( program );
for ( SnippetEvent snippetEvent : events ) {
System.out.println( snippetEvent.status() );
System.out.println( snippetEvent.value() );
System.out.println( snippetEvent.snippet().source() );
System.out.println( snippetEvent.snippet().kind() );
if ( snippetEvent.snippet() instanceof VarSnippet ) {
VarSnippet varSnippet = (VarSnippet) snippetEvent.snippet();
System.out.println( varSnippet.typeName() );
}
}
}
Die Ausgabe ist:
VALID 10000000000 java.math.BigInteger.TEN.pow( 10 ); VAR java.math.BigInteger
Ein paar Dinge sind an der API bemerkenswert, und zwar die Typen und Ergebnisse: sie sind Strings. varSnippet.typeName() ist ein String und snippetEvent.value() ebenso. Es ist nicht möglich, eine echte Objektrepräsentation zu bekommen, was die Nutzung als eingebettete Skriptsprache einschränkt.
Weitere Informationen lassen sich aus dem JEP 222: jshell: The Java Shell (Read-Eval-Print Loop)[1] entnehmen und von den Quellen, die bei Java 9 dabei sind. Fragen über das Produkt lassen sich in der Mailingliste http://mail.openjdk.java.net/mailman/listinfo/kulla-dev stellen.
[1] http://openjdk.java.net/jeps/222
Oracle definiert vier Treiberkategorien, die wir im Folgenden beschreiben. Sie unterscheiden sich im Wesentlichen darin, ob sie über einen nativen Anteil verfügen oder nicht.
ODBC (Open Database Connectivity Standard) ist ein Standard von Microsoft, der den Zugriff auf Datenbanken über eine genormte Schnittstelle möglich macht. ODBC ist insbesondere in der Windows-Welt weit verbreitet, und für jede ernst zu nehmende Datenbank gibt es einen Treiber.
Da es am Anfang der JDBC-Entwicklung keine Treiber gab, haben sich JavaSoft und Intersolv (seit 2000 Merant) etwas ausgedacht: eine JDBC-ODBC-Brücke, die die Aufrufe von JDBC in ODBC-Aufrufe der Client-Seite umwandelt. Da die Performance oft nicht optimal und die Brücke nicht auf jeder Plattform verfügbar ist, stellt diese JDBC-Anbindung häufig eine Notlösung dar. Und weil ODBC eine systembezogene Lösung ist, hat der Typ-1-Treiber native Methoden, was die Portierung und seinen Einsatz – etwa über das Netz – erschwert. Die JDBC-ODBC-Brücke implementiert seit Version 1.4 den JDBC 2-Standard. In Java 8 wurde die Brücke entfernt, ist also kein Teil mehr vom JDK. Es gibt existierende Lösungen auf dem Markt, allerdings gibt es keine wirkliche Notwendigkeit mehr dafür, da es für jede bedeutende Datenbank einen direkten JDBC-Treiber gibt.
Diese Treiber übersetzen die JDBC-Aufrufe direkt in Aufrufe der Datenbank-API. Dazu enthält der Treiber Programmcode, der native Methoden aufruft.
Treiber vom Typ 1 oder 2 sind nicht portabel auf jeder Plattform, da sie zum einen für die JDBC-ODBC-Brücke auf die Plattform-Bibliothek für ODBC zurückgreifen müssen und zum anderen auf plattformspezifische Zugriffsmöglichkeiten für die Datenbank angewiesen sind. Den Treiber auf einen anderen Computer zu übertragen funktioniert in der Regel nicht, da sie eng mit der Datenbank verbunden sind. Läuft auf einem Server etwa eine alte dBASE-Datenbank und ein nativer Typ-2-Treiber greift direkt auf die lokale Datenbank zu, dann kann dieser JDBC-Treiber nicht einfach auf einen Tablet-PC mit ARM-Prozessor kopiert werden – zum einen würde er dort wegen der anderen Hardware-Architektur nicht laufen, und zum anderen würde der Treiber dann wohl kaum über das Netzwerk auf die Datenbank zugreifen können.
Der universelle JDBC-Treiber ist ein in Java programmierter Treiber, der beim Datenbankzugriff auf den Client geladen wird. Der Treiber kommuniziert mit der Datenbank nicht direkt, sondern über eine Softwareschicht, die zwischen der Anwendung und der Datenbank sitzt: der Middleware. Damit erfüllen Typ-3-Treiber eine Vermittlerrolle, denn erst die Middleware leitet die Anweisungen an die Datenbank weiter. Für Applets und Internetdienste hat ein Typ-3-Treiber den großen Vorteil, dass seine Klassendateien oft kleiner als Typ-4-Treiber sind, da ein komprimiertes Protokoll eingesetzt werden kann. Über das spezielle Protokoll zur Middleware ist auch eine Verschlüsselung der Verbindung möglich. Kaum eine Datenbank unterstützt verschlüsselte Datenbankverbindungen. Da zudem das Middleware-Protokoll unabhängig von der Datenbank ist, müssen auf der Client-Seite für einen Datenbankzugriff auf mehrere Datenbanken auch nicht mehr alle Treiber installiert werden, sondern im günstigsten Fall nur noch ein Typ-3-Treiber von einem Anbieter. Die Ladezeiten sind damit deutlich geringer.
Diese Treiber sind vollständig in Java programmiert und kommunizieren direkt mit dem Datenbankserver. Sie sprechen mithilfe des datenbankspezifischen Protokolls direkt mit der Datenbank über einen offenen IP-Port. Dies ist in einer Direktverbindung die performanteste Lösung. Sie ist jedoch nicht immer realisierbar, etwa bei Datenbanken wie MS Access, dBase oder Paradox, die kein Netzwerkprotokoll definieren.
Um dynamische Programmiersprachen wie JavaScript performant auf die JVM zu bringen wurde in Java 7 der neue Bytecode invokedynamic eingeführt und das Paket lang.invoke. Java8 greift zur Umsetzung der Lambda-Ausdrücke massiv auf invokedynamic zurück.
Aufgabe des Pakets ist es, dynamisch Methoden schnell aufzurufen, und zwar deutlich schneller als Reflection das kann, weil in der JVM der Methodenaufruf so optimiert wird wie ein ganz normaler Methodenaufruf auf ein Zielobjekt. Dazu müssen aber die Typen exakt vorliegen, sodass es schärfere Anforderungen gibt als bei Reflection, dort ist z. B. der Rückgabetyp beim Aufruf irrelevant.
Um einen dynamischen Methodenaufruf zu starten ist zunächst eine exakte Beschreibung der Rückgabe- und Parametertypen nötig – das übernimmt ein MethodType-Objekt. Ist das aufgebaut, wird ein MethodHandle erfragt, ein getypter, direkt ausführbarer Verweis auf die repräsentierte Methode. Die MethodHandle-Methode invoke(…) führt dann den Aufruf mit gegebenen Argumente auf.
Dazu ein Beispiel. Wir möchten auf einem Rectangle-Objekt die Methode union(Rectangle) aufrufen, um als Ergebnis ein neues Rectangle zu bekommen, was die beiden Rechtecke vereinigt.
Object rect1 = new Rectangle( 10, 10, 10, 10 ); String methodName = "union"; Class<?> resultType = Rectangle.class; Object rect2 = new Rectangle( 20, 20, 100, 100 ); Class<? > parameterType = rect2.getClass();
rect1 ist das eigentliche Objekt auf dem die methodName aufgerufen werden soll. resultType ist der Ergebnistyp den wir von der Methode erwarten, als Class-Objekt. rect2 ist das Argument für union(…). Der parameterType für die union(…)-Methode ergibt sich aus dem Class-Objekt vom rect2.
MethodType mt = MethodType.methodType( resultType, parameterType ); MethodHandle methodHandle = MethodHandles.lookup().findVirtual( rect1.getClass(), methodName, mt ); System.out.println( methodHandle.invoke( rect1, rect2 ) );
Als erstes erfragen wir MethodType und geben Ergebnis- und Parametertyp an; noch nicht den Methodennamen, hier geht es nur um die Typen. Der MethodHandle verheiratet den Methodennamen und die Typangaben mit einer Klasse, die so eine Methode anbietet. invoke(…) führt letztendlich den Aufruf aus; das erste Argument ist das „this“-Objekt, also das Objekt auf dem die Methode aufgerufen werden soll, als nächstes folgenden die Argumente von union(…), also das zweite Rechteck. Als Ergebnis bekommen wir das Rectangle-Objekt, was genau der Vereinigung entspricht.
Gerade habe ich angefangen mein JDBC-Kapitel zu aktualisieren, weg von HSQLDB hin zur mitgelieferten Java DB. Ich habe die Java DB-Datenbank gestartet, den Text und Beispiele umgeschrieben, usw. Irgendwie habe ich das db-Verzeichnis von Java 8 genutzt, ohne das mir das aufgefallen wäre. Das Kapitel ist fertig gewesen, da wollte ich schauen, ob Java 9 die aktuelle Version von Apache DB nutzt und was ist? Arrrg. In Java 9 ist die Java DB rausgeflogen. Verdammt. Alle Änderungen wieder rückgängig machen, es bleibt vorerst bei HSQLDB.
Wie man an den Beiträgen hier im Blog erkennen kann, sind viele Absätze aktualisiert worden mit den Methoden aus Java 9. Der Kleinkram ist komplett beschrieben, was jetzt noch fehlt:
Im Allgemeinen war das Update auf Java 9 in Ordnung, newInstance() auf Class habe ich öfters gebraucht, das ist jetzt deprecated. Mein Bsp. mit der Javadoc API funktioniert nicht mehr, das Modulsystem macht mir einen Strich durch das Programm.
Mit der Gewichtung Swing/Java FX bin ich nicht zufrieden. Java FX ist weiterhin kein Teil der offiziellen Java SE, sondern nur Teil vom Oracle/Open JDK. Außerdem ist die Zukunft auch hier unsicher, jetzt, wo schon so viele Entwickler vom Projekt. abgezogen wurden. Der Trend geht ganz klar Richtung Web, sodass ich eigentlich beide Kapitel massiv kürzen sollte bis nur noch eine Einleitung stehen bleibt und noch mal ein Web-Kapitel ergänzen sollte. Was ist eure Meinung?
Seit Java 9 müssen die statischen und Default-Methoden nicht mehr public sein, sie können auch private sein. Das ist gut, denn das beugt Codeduplikate vor; mit privaten Methoden können Programmteile innerhalb der Schnittstelle ausgelagert werden. Private Methoden bleiben natürlich in der Schnittstelle und werden nicht in die implementierenden Klassen vererbt.
Der Stack-Trace, den Java über die StackTraceElement bietet, ist relativ arm an Informationen, und die Standardausgabe über die Throwable-Methode printStackTrace(…) ist unübersichtlich. Aus Performance-Gründen können sogar Einträge fehlen, so dokumentiert es die Javadoc an der Methode:
Some virtual machines may, under some circumstances, omit one or more stack frames from the stack trace. In the extreme case, a virtual machine that has no stack trace information concerning this thread is permitted to return a zero-length array from this method.
Zudem fehlen spannende Informationen wie Zeitpunkte, und ein Thread.getAllStackTraces() ist bei vielen Threads und tiefen Aufrufhierarchien sehr langsam. Summa summarum: Ein guter Debugger oder ein Analysetool mit Zugriff auf die JVM-Eigenschaften ist für die ernsthafte Entwicklung unumgänglich.
In Java 9 hat Oracle die “JEP 259: Stack-Walking API” umgesetzt. Ein java.lang.StackWalker wandert die Aufrufhierarchie ab und repräsentiert die Aufrufhierarchie als StackFrame-Objekte von dort, wo der Stack generiert wurde, nach unten zur Aufrufstelle. Es gibt mehrere überladenen statische getInstance(…)-Methoden, die einen StackWalker generieren. Wir können dann mit
über die StackFrames laufen.
Beispiel:
public static void showTrace() {
List<StackFrame> frames =
StackWalker.getInstance( Option.RETAIN_CLASS_REFERENCE )
.walk( stream -> stream.collect( Collectors.toList() ) );
for ( StackFrame stackFrame : frames )
System.out.println( stackFrame );
}
Da alle Informationen zu liefern die Geschwindigkeit senkt, und vielleicht unnötig viel Arbeit verursacht, deklariert die Aufzählung StackWalker.Option die Konstanten RETAIN_CLASS_REFERENCE, SHOW_HIDDEN_FRAMES, SHOW_REFLECT_FRAMES für unterschiedliche Vollständigkeit der Informationen. Die Aufzählungen sind ein Argument für getInstance(…). Um die Class-Objekte über getDeclaringClass() abrufen zu können, muss getInstance(…) mit der Option.RETAIN_CLASS_REFERENCE gesetzt sein.
Beispiel: Die forEach(…)-Methode eines Streams konsumiert einen Consumer auf jedem Element. Finde heraus, ob unsere Consumer-Methode accept(…) indirekt von einer Klasse aus dem Paket java.util.concurrent aufgerufen wurde:
Consumer<String> walkStack = String -> {
StackWalker.getInstance( Option.RETAIN_CLASS_REFERENCE )
.walk( stream -> stream.map( StackFrame::getDeclaringClass )
.map( Class::getPackageName )
.filter( s -> s.startsWith( "java.util.concurrent" ) )
.findFirst() )
.ifPresentOrElse( e -> System.out.println( "Durch j.u.c gelaufen" ),
() -> System.out.println( "Nicht durch j.u.c gelaufen" ) );
};
Stream.of( "Hallo" ).forEach( walkStack ); // Nicht durch j.u.c gelaufen
Stream.of( "Hallo" ).parallel().forEach( walkStack ); // Durch j.u.c gelaufen
Ein ProcessHandle ist ein neuer Typ in Java 9, der native Prozesse identifiziert. Wir bekommen Exemplare entweder über
Jeder Prozess verfügt über eine identifizierende long-Ganzahl, die getPid() auf dem ProcessHandle liefert. Weitere Details zu den Startparametern offenbare ProcessHandle.Info-Ojekte.
Beispiel: Gib alle vorhanden Informationen über alle Prozesse aus:
Consumer<ProcessHandle> log = handle -> System.out.printf( "PID=%s, Root?=%s, Info=%s%n", handle.getPid(), !handle.parent().isPresent(), handle.info() ); ProcessHandle.allProcesses().forEach( log );
Die Ausgabe kann so aussehen:
PID=0, Root?=true, Info=[] PID=4, Root?=true, Info=[] ... PID=4368, Root?=true, Info=[] PID=4568, Root?=true, Info=[user: Optional[Yoga\Christian], cmd: C:\Windows\System32\sihost.exe, startTime: Optional[2017-02-04T20:54:07.601Z], totalTime: Optional[PT3M39.703125S]] PID=4592, Root?=true, Info=[user: Optional[Yoga\Christian], cmd: C:\Windows\System32\svchost.exe, startTime: Optional[2017-02-04T20:54:07.621Z], totalTime: Optional[PT14.9375S]] PID=4628, Root?=true, Info=[] ...
ProcessHandle implementiert vernünftig equals(…) und auch Comparable<ProcessHandle>; die Sortierung ist nach der Prozess-ID.
Methoden-Delegation
Einige Methoden aus Process delegieren an den assoziierten ProcessHandle. Die Methoden heißen gleich.
| Process-Methoden | Implementierung |
| long getPid() | return toHandle().getPid(); |
| ProcessHandle.Info info() | return toHandle().info(); |
| Stream<ProcessHandle> children() | return toHandle().children(); |
| Stream<ProcessHandle> descendants() | return toHandle().descendants(); |
Weiterhin gibt es onExit(), supportsNormalTermination(), isAlive(), destroy() und destroyForcibly() auf beiden Typen Process und ProcessHandle.
Über Objekte vom Typ ProcessHandle.Info lassen sich weitere Details zum Prozess erfragen; die Rückgaben sind Optional, weil die Informationen vielleicht nicht vorliegen.
Mit Methoden von Process lässt sich der Status des externen Programms erfragen und verändern. Die Methode waitFor(…) lässt den eigenen Thread so lange warten, bis das externe Programm zu Ende ist, oder löst eine InterruptedException aus, wenn das gestartete Programm unterbrochen wurde. Der Rückgabewert von waitFor() ist der Rückgabecode des externen Programms, eine zweite Variante von waitFor(…) wartet eine gegebene Zeit. Wurde das Programm schon beendet, liefert auch exitValue() den Rückgabewert. Soll das externe Programm (vorzeitig) beendet werden, lässt sich die Methode destroyXXX() verwenden; das eigene Java-Programm kann nicht beendet werden.
abstract class java.lang.Process
Immer dann, wenn ein Scanner mit einem regulären Ausdruck konfiguriert wurde, wird intern der Zustand vom dafür zugewiesenen Matcher aktualisiert. Die Scanner-Methode match() liefert einen MatchResult der letzten Operation, allerdings folgt eine IllegalStateException, wenn es keinen Match gab oder der letzte Match nicht erfolgreich war.
Beispiel: Finde alles, was im inneren zwischen <b></b> steht:
Scanner sc = new Scanner( "Wichtig: <b>essen</b> und <b>trinken</b>!"; while ( sc.findInLine( "<b>(.+?)</b>" ) != null ) System.out.println( scanner.match().group( 1 ) );
Die Ausgabe ist dann „essen“ und „trinken“.
Java 9 bringt die neue Methode Stream<MatchResult> findAll(Pattern pattern)/ findAll(String patString) mit.
Beispiel: Finde alles, was im inneren zwischen <b></b> steht:
new Scanner( "<b>essen</b> und\n <b>trinken</b>" ) .findAll( "<b>(.+?)</b>" ) .forEach( matchresult -> System.out.println( matchresult.group( 1 ) ) );
Die Ausgabe ist ebenfalls „essen“ und „trinken“.
Die in Java 9 eingeführte Objektmethode stream() ist eine sehr gute Ergänzung, denn sie liefert einen Stream<String> von zerlegten Strings.
Beispiel: Durch Komma getrennte String sollen durch ein Zeilenumbruch wieder zusammengefügt werden:
String s = "CNN, Politico, LA Times, New York Times";
System.out.println( new Scanner(s).useDelimiter( "\\s*,\\s*" ).tokens()
.collect( Collectors.joining("\n") ) );
Im Kapitel über Ausnahmen haben wir schon auf die Notwendigkeit hingewiesen, Wertebereiche zu prüfen und im Fehlerfall Ausnahmen wie IllegalArgumentException oder IndexOutOfBoundsException auszulösen um keine falschen Werte in das Objekt zu lassen.
In Java 9 sind drei Methoden hinzugekommen, die die gültigen Werbereiche von Index-basierten Methoden prüfen können und im Fehlerfall eine IndexOutOfBoundsException auslösen.
class java.util.Objects
Beispiel: Implementierung der get(int)-Methode in ArrayList:
public E get(int index) {
Objects.checkIndex(index, size);
return elementData(index);
}
Für Geldbeträge gibt es in Java keinen eigenen Datentyp und so kann eine Speicherung je nach Programm immer anders aussehen. Es bieten sich an:
Hinweis: Die primitiven Datentypen double und float sind wegen ihrer Unfähigkeit Vielfaches von 0.01 korrekt dazustellen nicht empfohlen; Rundungsfehler treten schnell auf.
Im „JSR 354: Money and Currency API“ wird ein eigener Datentyp für Geldbeträge definiert, und die Typen sollen eigentlich in Java 9 aufgenommen werden, doch dazu kam es nicht. Dennoch sind die Typen interessant und die Referenzimplementierung Moneta ein Blick wert: http://javamoney.github.io/ri.html. Neben Geldbeträgen erlaubt die kleine Bibliothek auch Umrechungen, Formatierungen und eigene Währungen.
Währungen werden durch Währungscodes beschrieben, und die Definition findet sich in der Norm ISO 4217. Einige ISO-Codes sind:
| ISO 4217-Code | Währung/Einheit | Land |
| EUR | Euro | Länder der europäischen Währungsunion |
| CNY | Renminbi | China |
| DKK | Krone | Dänemark |
| GBP | Pfund | Vereinigtes Königreich |
| INR | Rupie | Indien |
| USD | Dollar | USA, Ecuador, … |
| XAU | Feinunze Gold |
Einige ISO 4217-Codes
Die Tabelle lässt am letzten Eintrag erkennen, dass es auch ISO-Codes für Edelmetalle und sogar Fonds gibt. Für jedes Kürzel gibt es ebenfalls einen numerischen Code.
Java repräsentiert Währungen durch die Klasse java.util.Currency. Exemplare der Klasse werden durch eine Fabrikmethode getInstance(String currencyCode) erfragt, bzw. aus einer Aufzählung ausgewählt.
Beispiel: Gib alle im System angemeldeten Währungen mit ein paar Informationen aus:
Currency.getAvailableCurrencies().stream() .sorted( Comparator.comparing( Currency::getCurrencyCode ) ) .forEach( c -> System.out.printf( "%s, %s, %s, %s%n", c.getCurrencyCode(), c.getSymbol(), c.getDisplayName(), c.getNumericCode() ) );
Die Ausgabe beginnt so:
ADP, ADP, Andorranische Pesete, 20
AED, AED, VAE-Dirham, 784
AFA, AFA, Afghanische Afghani (1927–2002), 4
AFN, AFN, Afghanischer Afghani, 971
ALL, ALL, Albanischer Lek, 8
…
Neu in Java 9 sind weiterhin diverse Methoden
Sie geben den Index auf das erste Element zurück was ungleich ist. Sind beide Felder gleich ist die Rückgabe -1.
Für Objekt-Array gibt es weiterhin:
Die erste/zweite Methode nutzt direkt equals(…), die dritte/vierte einen externen Comparator.
Diverse in Java 9 eingeführte int compareXXX(XXX[] a, XXX[] b)-Methoden gehen die Arrays ab und testen alle Paare auf ihre Ordnung. Es gibt die von Comparator bekannte Rückgabe: ist jedes a[i] == b[i] ist die Rückgabe 0. Ist in der Abfragefolge ein a[i] kleiner als b[i] ist, dann ist die Rückgabe negativ, ist ein a[i] größer als b[i] ist die Rückgabe positiv. Die Methode ist überladen mit einer Variante, die einen Bereich im Array auswählt: compare(XXX[] a, int aFromIndex, int aToIndex, XXX[] b, int bFromIndex, int bToIndex). Für byte, short, int und long gibt es weiterhin eine Vergleichsmethode ohne Vorzeichen über den gesamten Wertebereich:
Für Objekte gibt es eigene Methoden:
Die Arrays.equals(…)-Methode kann auch beliebige Objektfelder vergleichen, doch nutzt sie dann nicht die Identitätsprüfung per ==, sondern die Gleichheit per equals(…). Eine seit Java 9 hinzugekommene Methode fragt einen Comparator.
Beispiel
Enthalten zwei String-Arrays die gleichen Wörter, wobei Groß-/Kleinschreibung keine Rolle spielt?
String[] words1 = { "Zufriedenheit", "übertrifft" , "Reichtum" };
String[] words2 = { "REICHTUM", "übertrifft" , "ZuFRIEDEnheit" };
Arrays.sort( words1, String.CASE_INSENSITIVE_ORDER );
Arrays.sort( words2, String.CASE_INSENSITIVE_ORDER );
System.out.println( Arrays.equals( words1, words2, String.CASE_INSENSITIVE_ORDER ) );
class java.util.Arrays
Die statischen Methoden Arrays.equals(…) vergleichen, ob zwei Arrays die gleichen Inhalte besitzen; dazu ist die überladene Methode für alle wichtigen Typen definiert. Wenn zwei Arrays tatsächlich die gleichen Inhalte besitzen, ist die Rückgabe der Methode true, sonst false. Natürlich müssen beide Arrays schon die gleiche Anzahl von Elementen besitzen, sonst ist der Test sofort vorbei und das Ergebnis false. Im Fall von Objekt-Arrays nutzt Arrays.equals(…) nicht die Identitätsprüfung per ==, sondern die Gleichheit per equals(…).
Beispiel
Vergleiche drei Arrays:
int[] array1 = { 1, 2, 3, 4 };
int[] array2 = { 1, 2, 3, 4 };
int[] array3 = { 9, 9, 2, 3, 9 };
System.out.println( Arrays.equals( array1, array2 ) ); // true
System.out.println( Arrays.equals( array2, 1, 3, array3, 2, 4 ) ); // true
Ein Vergleich von Teil-Arrays ist erst in Java 9 hinzugekommen.
Bei unterreferenzierten Arrays betrachtet Arrays.equals(…) das innere Array als einen Objektverweis und vergleicht es auch mit equals(…) – was jedoch bedeutet, dass nicht identische, aber mit gleichen Elementen referenzierte innere Arrays als ungleich betrachtet werden. Die statische Methode deepEquals(…) bezieht auch unterreferenzierte Arrays in den Vergleich ein.
Beispiel
Unterschied zwischen equals(…) und deepEquals(…):
int[][] a1 = { { 0, 1 }, { 1, 0 } };
int[][] a2 = { { 0, 1 }, { 1, 0 } };
System.out.println( Arrays.equals( a1, a2 ) ); // false
System.out.println( Arrays.deepEquals( a1, a2 ) ); // true
System.out.println( a1[0] ); // zum Beispiel [I@10b62c9
System.out.println( a2[0] ); // zum Beispiel [I@82ba41
Dass die Methoden unterschiedlich arbeiten, zeigen die beiden letzten Konsolenausgaben: Die von a1 und a2 unterreferenzierten Arrays enthalten die gleichen Elemente, sind aber zwei unterschiedliche Objekte, also nicht identisch.
Hinweis
deepEquals(…) vergleicht auch eindimensionale Arrays:
Object[] b1 = { "1", "2", "3" };
Object[] b2 = { "1", "2", "3" };
System.out.println( Arrays.deepEquals( b1, b2 ) ); // true
class java.util.Arrays