Expression Language 3.0 (JSR-341), wow, aber komplett an mir vorbeigezogen

Schau http://www.youtube.com/watch?v=Uyx7fLXXSS4, http://www.youtube.com/watch?v=JEKpRjXL06w und http://rijkswatch.blogspot.de/2012/06/expression-language-30-is-in-public.html. Aus der Spezifikation http://download.oracle.com/otn-pub/jcp/el-3_0-fr-eval-spec/EL3.0.FR.pdf.

  • Lambdas: fact = n -> n==0? 1: n*fact(n-1); fact(5) oder employees.where(e->e.firstName == ‘Bob’)
  • Collection-Syntax: employees.where(e->e.firstName == ‘Bob’) oder {„one“:1, „two“:2, „three“:3}
  • Streaming wie in Java 8: books.stream().filter(b->b.category == ‘history’) .map(b->b.title) .toList() oder [1,2,3,4,5].stream().substream(2,4).toArray()

Insgesamt:

■ Added Chapter 2 “Operations on Collection Objects”.
■ Added 1.8, String concatenation operator.
■ Added 1.13, Assignment operator.
■ Added 1.14, Semi-colon operator.
■ Added 1.20 Lambda Expression.
■ Added 1.22 Static Field and Methods.
■ Added T and cat to 1.17 Reserved words.
■ Modified 1.16 Operator precedence.
■ Modified coercion rule from nulls to non-primitive types.
■ Many changes to the javadoc API.

Schön ist, dass man das auch eigenständig außerhalb vom Web-Container verwenden kann. Das macht die EL interessant für Template-Lösungen.

Siehe auch YouTube Video: https://www.youtube.com/watch?v=JEKpRjXL06w

Überblick: Aktuelle Versionen der Java EE 7 Technologien

Von https://blogs.oracle.com/theaquarium/entry/java_ee_7_platform_completes:

JSRs:

  • Java Platform, Enterprise Edition 7 (JSR 342)
  • Concurrency Utilities for Java EE 1.0 (JSR 236)
  • Java Persistence 2.1 (JSR 338)
  • JAX-RS: The Java API for RESTful Web Services 2.0 (JSR 339)
  • Java Servlet 3.1 (JSR 340)
  • Expression Language 3.0 (JSR 341)
  • Java Message Service 2.0 (JSR 343)
  • JavaServer Faces 2.2 (JSR 344)
  • Enterprise JavaBeans 3.2 (JSR 345)
  • Contexts and Dependency Injection for Java EE 1.1 (JSR 346)
  • Bean Validation 1.1 (JSR 349)
  • Batch Applications for the Java Platform 1.0 (JSR 352)
  • Java API for JSON Processing 1.0 (JSR 353)
  • Java API for WebSocket 1.0 (JSR 356)

MRs:

  • Web Services for Java EE 1.4 (JSR 109)
  • Java Authorization Service Provider Contract for Containers 1.5 (JACC 1.5) (JSR 115)
  • Java Authentication Service Provider Interface for Containers 1.1 (JASPIC 1.1) (JSR 196)
  • JavaServer Pages 2.3 (JSR 245)
  • Common Annotations for the Java Platform 1.2 (JSR 250)
  • Interceptors 1.2 (JSR 318)
  • Java EE Connector Architecture 1.7 (JSR 322)
  • Java Transaction API 1.2 (JSR 907)
  • JavaMail 1.5 (JSR 919)

MR steht für “Maintenance Releases”.

Kommandozeilenprogramm jcmd für Diagnosekommandos

Mit dem Kommandozeilenprogramm jcmd lassen sich Diagnosekommandos zu einer laufenden JVM schicken. Die Java-Programme werden wieder über eine PID identifiziert, die jcmd auch anzeigen kann:

$ jcmd.exe
2484 C:\Users\Christian\eclipse\\plugins/org.eclipse.equinox…
18868 sun.tools.jcmd.JCmd

Eclipse hat die PID 2484 und das Tool selbst – das bei jedem Starten natürlich eine neue PID bekommt – 18868.

Interessant wird jcmd dadurch, dass sich Diagnose-Kommandos senden lassen. Als erstes steht die PID, dann folgt das Kommando. Um ein Übersicht über die Häufigkeit von geladen Klassen zu bekommen ist GC.class_histogram zu nutzen:

$ jcmd.exe 2484 GC.class_histogram
2484:
num #instances #bytes class name
----------------------------------------------
1: 600676 40906520 [C
2: 549996 13199904 java.lang.String
3: 121570 4862800 java.util.WeakHashMap$Entry
4: 117120 3747840 java.lang.ref.WeakReference
5: 63926 2730616 [Ljava.lang.String;

Auf der Hilfeseite sind die Kommandos nicht aufgeführt, weil sie abhängig von der jeweiligen JVM sind und nicht im Tool jcmd selbst verankert sind. Daher müssen sie dynamisch von einem laufenden Java-Programm erfragt werden. Unser Elcipse-Prozess hatte die PID 2484, und dann kommt die Option help zum Einsatz:

$ jcmd.exe 2484 help
2484:
The following commands are available:
VM.native_memory
VM.check_commercial_features
VM.unlock_commercial_features
ManagementAgent.stop
ManagementAgent.start_local
ManagementAgent.start
Thread.print
GC.class_stats
GC.class_histogram
GC.heap_dump
GC.run_finalization
GC.run
VM.uptime
VM.flags
VM.system_properties
VM.command_line
VM.version
help
For more information about a specific command use 'help <command>'.

Wie die letzte Zeile verrät, gibt ein angehängtes Kommando weitere Informationen, etwa

$ jcmd.exe 2484 help GC.heap_dump
2484:
GC.heap_dump
Generate a HPROF format dump of the Java heap.
Impact: High: Depends on Java heap size and content. Request a full GC unless th
e '-all' option is specified.
Permission: java.lang.management.ManagementPermission(monitor)
Syntax : GC.heap_dump [options] <filename>
Arguments:
filename : Name of the dump file (STRING, no default value)
Options: (options must be specified using the <key> or <key>=<value> syntax)
-all : [optional] Dump all objects, including unreachable objects (BOOLE
AN, false)

Was ist eine Oracle JDK/JRE Certified System Configuration?

Eine ideale, perfekt getestete und unterstützte Umgebung gilt als Oracle JDK/JRE Certified System Configuration. Das ist eine Kombination aus Betriebssystem mit installierte Service-Packs; das beschreibt das Unternehmen unter http://www.oracle.com/technetwork/java/javase/config-417990.html für Java 7. Bei gemeldete Bugs auf nicht zertifizierten Plattformen kann dann schnell ein „Sorry, das ist eine nicht-unterstützte Plattform, das schauen wir uns nicht weiter an“ folgen. Bei Linux ist zum Beispiel Ubuntu-Linux markiert als „Not certified on Oracle VM“. Das heißt nicht, dass es dort nicht zu 100% läuft, nur, dass im Fehlerfall eben kein Fix geben muss.

JavaScript, Rhino und Nashorn

Java ist zwar eine tolle Allround-Programmiersprache, aber die explizite Typisierung und der Zwang, Klassen und Methoden zu deklarieren, machen Java nicht wirklich attraktiv für Skripte, wo eher die Kompaktheit zählt und wo keine lang zu pflegenden Programme entstehen. Um die JVM und die Java-Bibliotheken auch zur Automatisierung von Abläufen einzusetzen, lässt sich neben der Java-Syntax auch alternative Programmiersprachen einsetzen.

JavaScript ist eine flexible interessante Programmiersprache, die mittlerweile auch außerhalb von Browsern populär ist, serverseitig eingesetzt wird und auch für die Entwicklung von Desktop-Anwendung – siehe Gnome 3, WinRT, Chrome Apps oder FirefoxOS.

Das Oracle JDK/OpenJDK bzw. das JRE[1] bringt ab Java 6 neben dem Java-Compiler eine JavaScript-Engine und ein Kommandozeilentool mit, das Sprite ausführt. So lässt sich JavaScript als alternative Programmiersprache einsetzen – die Sprache ist also auswechselbar und steht versinnbildlicht auf den Bibliotheken und der Laufzeitumgebung. Auch lässt sich die JavaScript-Umgebung in eigene Java-Programme einbetten und integrieren.

Die Java-Distribution liefert eine JavaScript-Engine aus, die sich allerdings von Java 6 auf Java 8 verändert hat.

· Java 6, Java 7: Rhino (https://developer.mozilla.org/en-US/docs/Rhino). Die JavaScript-Engine – in Java programmiert – kommt von Mozilla und basiert auf einer Implementierung von Netscape, die ihren Browser damals komplett in Java schreiben wollten. Die Browser-Implementierung „Javagator“ wurde zwar eingestellt, doch die JavaScript-Umgebung lebte weiter und Sun lizenzierte die Technologie und bettete sie (minimal verändert) in Java 6 und Java 7 ein.

· Ab Java 8: Nashorn (http://openjdk.java.net/projects/nashorn/, http://openjdk.java.net/jeps/174). Oracle entwickelte von Grund auf eine ganz neue JavaScript-Engine, die den Code ohne Interpretation direkt in Bytecode übersetzt und eine exzellente Performance und Kompatibilität mit ECMAScript-262 Edition 5.1 zeigt. Obwohl die JavaScript Edition 6 noch keine Rolle spielt, unterstützt Nashorn einige Spracherweiterungen, etwa Abkürzungen für Lambda-Ausdrücke. Da Nashorn nur JavaScript selbst unterstützt, Rhino aber noch einige Mozilla-Bibliotheken, kommt es zu Inkompatibilitäten, falls Nashorn unreines JavaScript ausführen soll.


[1] Bei anderen Java SE-Implementierungen muss das nicht zwingend gegeben sein.

Java SE 7 Update 25

https://blogs.oracle.com/java/entry/java_se_7_update_25. Mit den üblichen Security-Udates und sonst noch ein paar Änderungen: http://www.oracle.com/technetwork/java/javase/7u25-relnotes-1955741.html. Nicht uninteressant finde ich folgendes, was die Kompatibilität von existierenden Anwendungen beeinflussen könnte:

Changes to Runtime.exec

On the Windows platform, the decoding of command strings specified tojava.lang.ProcessBuilder and the exec methods defined by java.lang.Runtime, has been made stricter since JDK 7u21. This may cause problems for applications that are using one or more of these methods with commands that contain spaces in the program name, or are invoking these methods with commands that are not quoted correctly. For more information see JDK 7u21 Release Notes.

In JDK 7u25, the system property jdk.lang.Process.allowAmbigousCommands can be used to relax the checking process and may be used as a workaround for some applications that are impacted by the stricter validation. The workaround is only effective for applications that are run without a security manager. To use this workaround, either the command line should be updated to include -Djdk.lang.Process.allowAmbigousCommands=true or the java application should set the system property jdk.lang.Process.allowAmbigousCommands to true.

Quoting and escaping commands on Windows platform is complicated. The following examples may be useful to developers if they are impacted by the stricter validation.

Example 1: The application needs to be launched with C:\Program Files\foo.exe.

Here are 3 possible ways:

Process p = new ProcessBuilder("c:\\Program File\\foo.exe").start();
Process p = Runtime.getRuntime().exec(new String[] { "c:\\Program File\\foo.exe" });
Process p = Runtime.getRuntime().exec("\"c:\\Program File\\foo.exe\"");

Where it is not possible to change the application to use one of the above approaches, then the system property jdk.lang.Process.allowAmbigousCommands may be used as a workaround.

Example 2: The application needs to launch "dir > dir.out".
This case requires launching cmd.exe, and also it needs the output to be redirected to a file. The best approach is to use the ProcessBuilder as shown in the following example:

Process p = new ProcessBuilder("cmd", "/C", "dir").redirectOutput(new File("dir.out")).start();

Where it not possible to change code to use ProcessBuilder or redirectOutput, then the following approaches can also be used:

Process p = new ProcessBuilder("cmd", "/C", "dir > dir.out").start();
Process p = Runtime.getRuntime().exec("cmd /C \"dir > dir.out\"");

Example 3: The application wants to launch a command with parameters that require special quoting; for example "log.bat \">error<\"".
Here are 3 possible ways to do this:

Process p = new ProcessBuilder("cmd", "/C", "log.bat", ">error<").start();
Process p = Runtime.getRuntime().exec(new String[] { "cmd", "/C", "log.bat", ">error<" })
Process p = Runtime.getRuntime().exec("cmd /C log.bat \">error<\"");

Mit RoboVM geht’s für Java in das iOS-Land

Normalerweise nimmt eine JVM den Bytecode der Hauptklasse, lädt zur Laufzeit Klassendateien nach, interpretiert diese und übersetzt sie Zwecks Optimierung in Maschinencode. Dem gegenüber steht ein anderer Ansatz, der sonst eher typisch für klassische Programmiersprachen ist, genannt ahead-of-time-Compilation. Bei diesem Verfahren ermittelt ein Werkzeug zunächst alle abhängigen Typen und generiert dann direkt Maschinencode für eine Plattform — kein Interpreter ist somit nötig. Für Java sind solche Compiler selten, aber wer Java auf iOS-Geräten nutzen möchte, kommt darum nicht herum, denn Apple gestattet keine interpretierten Sprachen auf den kleinen Geräten. Dafür springt RoboVM (http://www.robovm.org/) ein, eine Open-Source-Software unter der GPL- und Apache-Lizenz, die Java-Programme direkt in Maschinencode (ARM bzw. x86 Code) übersetzt. Zum Bauen der Zieldateien nutzt RoboVM unterschiedliche Tools, um eine Kette von Schritten zu realisieren: Zunächst übersetzt ein installierter LLVM den Bytecode in Assembler-Code, dann folgt ein Tool, was Assembler-Code in Binärcode umsetzt, und dann ein Linker für den letzten Schritt, der das Ergebnis zusammensetzt; Assembler und Linker kommen vom GCC. Die Übersetzung ist kein großer Akt, denn für RoboVM ist ein Eclipse-Plugin verfügbar, in dem die App auch – zumindest beim Mac – im Simulator gestartet werden kann.

RoboVM bietet den Entwicklern die Standard-Klassen der Java-Bibliothek sowie sie auch bei Android verfügbar sind und bildet die Klassen der iOS-Bibliothek (genannt Cocoa) auf Java ab. Weiterhin integriert es die Typen vom Android-Paket und die OpenGL ES API, sodass Android-Anwendungen auch unter iOS abgebildet werden können. Interessant wird das Projekt auch durch eine Umsetzung von JavaFX, sodass die proprietären Cocoa-Bibliotheken nicht verwendet werden müssen, und es im Prinzip möglich ist, die gleiche Java-Anwendung auf iOS oder Windows, Linux, … zum Laufen zu bringen. Bisher ist RoboVM in einer frühen Phase.

Die Java 8 JVM wird keine Tail Call Recursion Optimierung bekommen

Endrekursion (engl. Tail Call Recursion) ist eine Besonderheit bei Methoden, dass sie mit einem rekursiven Aufruf enden. Zum Einstieg: Ist die bekannte rekursive Implementierung der Fakultät endrekursiv?

int factorial(int n) {
  if ( n == 0 ) return 1;
  return n * factorial( n – 1 );
}

Zwar sieht es optisch so aus, als ob die factorial(int) mit einem Methodenaufruf an factorial(…) endet, doch findet hier keine Endrekursion statt, da nach dem Methodenaufruf noch eine Multiplikation stattfindet – etwas umgeschrieben ist es besser zu erkennen:

int factorial(int n) {
  if ( n == 0 ) return 1;
  int fac = factorial( n – 1 );
  return n * fac;
}

Die Berechnung der Fakultät lässt sich umschreiben, so dass tatsächlich eine Endrekursion sattfindet, und zwar durch Einführung eines Containers für Zwischenergebnisse, genannt Akkumulator:

int factorial( int n )
{
  return factorialTailrec( n, 1 );
}

private int factorialTailrec( int n, int accumulator )
{
  if (n == 0) return accumulator;
  return factorialTailrec( n – 1, n * accumulator );
}

Die umgeschriebene Version büßt gegenüber der ursprünglichen Version an Schönheit ein. Doch endrekursive Aufrufe sind attraktiv, da eine schlaue Übersetzungseinheit sie so optimieren kann, dass der rekursive Methodenaufruf durch einen Sprung ersetzt wird. In der Java Sprache haben wir keine direkten Sprünge, doch im Bytecode schon, sodass die Basis der Optimierung im Grunde so aussehen kann:

private int factorialTailrec( int n, int accumulator ) {
start:
  if ( n == 0 ) return accumulator;
  accumulator *= n;
  n--;
  goto start;
}

Die Rekursion ist durch eine ordinäre Schleife ersetzt, was Stack einspart und eine sehr gute Performance ergibt.

In funktionalen Programmen ergeben sich eher Situationen, in denen Endrekursion vorkommt, sodass es attraktiv ist, diese zu optimieren. Die Standard-JVM kann das bisher nicht, weil Java traditionell keine funktionale Programmiersprache ist, und Endrekursion eher selten vorkommt. Zwar wird die Optimierung von Endrekursion (engl. tail call optimization) immer wieder diskutiert und auch schon in Prototypen ausprobiert, aber nie  von der Oracle JVM implementiert. Für Entwickler heißt dass, rekursive Aufrufe nicht umzuschreiben in endrekursive Varianten, da sie sowieso nicht optimiert werden und nur unleserlicher würden, und bei großen Datenvolumen, sprich Stack-Tiefe, auf die übliche nicht-rekursive iterative Version umzustellen. Im Fall von factorialTailrec(..) kann dies nämlich auch vom Entwickler gemacht werden und sieht so aus:

private int factorialTailrec( int n, int accumulator ) {

while (n != 0) {

accumulator *= n;

n–;

}

return accumulator;

}

 

Einmal Finalizer, vielleicht mehrmals die automatischen Speicherbereinigung

Objekte von Klassen, die eine finalize()-Methode besitzen, kann Oracles JVM nicht so schnell erzeugen und entfernen wie Klassen ohne finalize(). Das liegt auch daran, dass die automatische Speicherbereinigung vielleicht mehrmals laufen muss, um das Objekt zu löschen. Es gilt zwar, dass der Garbage-Collector aus dem Grund finalize() aufruft, weil das Objekt nicht mehr benötigt wird, es kann aber sein, dass die finalize()-Methode die this-Referenz nach außen gibt, sodass das Objekt wegen einer bestehenden Referenz nicht gelöscht werden kann und so zurück von den Toten geholt wird. Das Objekt wird zwar irgendwann entfernt, aber der Finalizer läuft nur einmal und nicht immer pro GC-Versuch. Einige Hintergründe erfährt der Leser unter http://www.iecc.com/gclist/GC-lang.html#Finalization.

Löst eine Anweisung in finalize() eine Ausnahme aus, so wird diese ignoriert. Das bedeutet aber, dass die Finalisierung des Objekts stehen bleibt. Die automatische Speicherbereinigung beeinflusst das in ihrer Arbeit aber nicht.

ThreadLocalRandom als schneller paralleler Zufallszahlengenerator

Zufallszahlen sind immer nur Pseudozufallszahlen und werden mit einer mathematischen Formel aus dem Vorgänger generiert. Der Vorgänger muss dabei gespeichert werden, und das ist die Aufgabe eines Random-Objekts. Die Methode Math.random() nutzt intern ein Random-Objekt, und jetzt kann es zu Wartezeiten kommen, wenn mehrere Threads gleichzeitig random() aufrufen, denn die Methode darf intern ja nur einen Thread die letzte Zufallszahl schreiben lassen. Math ist also eine Klasse mit Zustand und Zustandsverwaltung ist bei Multithreaded-Anwendungen immer etwas speziell.

Um Zufallszahlen schnell generieren zu können, sind diese Verzögerungen ungünstig, und es gibt zwei Lösungen dafür. Einmal lässt sich pro Thread ein Random-Objekt generieren, sodass es im Code der Random-Klasse dann keine Konkurrenzsituation geben kann. Aber optimal ist das noch nicht, denn der Programmcode der Random-Klasse ist auf diese Nebenläufigkeit vorbereitet, und bei nur einem Thread wäre ein schlankerer Programmcode besser, der für eine Single-Threaded Abarbeitung optimiert ist. Und hier kommt die Klasse java.util.concurrent.ThreadLocalRandom ins Spiel. Sie ist eine Unterklasse von Random und überschreibt die eigentliche Generator-Methode next(int)-Methode so, dass es keine Synchronisation gibt; die Ausführung in Multithreaded-Umgebung ist dementsprechend schnell.

Beispiel: Erzeuge Zufallszahlen zwischen 1 und 10:

ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
System.out.println( threadLocalRandom.nextInt( 1, 10 + 1 ) );
System.out.println( threadLocalRandom.nextInt( 1, 10 + 1 ) );

Die Variable threadLocalRandom kann problemlos zwischen verschiedenen Threads „geteilt“ werden.

Die Klasse ThreadLocalRandom erbt alle Methoden von Random, und überschreibt etwa die neuen Methoden aus Java 8, die einen Stream von Zufallszahlen liefern. Desweiteren kommen einige neue Methoden hinzu, um etwa Zufallszahlen in einem gewissen Bereich zu generieren – das fehlt in Random. Ein neuer Seed kann nicht gesetzt werden, ThreadLocalRandom überschreibt setSeed(long) so, dass eine UnsupportedOperationException ausgelöst wird.

class java.util.concurrent.ThreadLocalRandom
extends Random

§ static ThreadLocalRandom current()
Liefert das aktuelle ThreadLocalRandom-Objekt.

§ void setSeed(long seed)
Nicht unterstützt, löst UnsupportedOperationException aus.

§ double nextDouble(double n)

§ double nextDouble(double least, double bound)

§ double nextGaussian()

§ int nextInt(int least, int bound)

§ long nextLong(long n)

§ long nextLong(long least, long bound)
Liefert Zufallszahl und aktualisiert den Seed.

§ DoubleStream doubles()

§ IntStream ints()

§ LongStream longs()

§ DoubleStream gaussians()
Liefert einen Stream von Daten.

§ protected int next(int bits)
Liefert die nächste Zufallszahl, eine interne Methode, die ThreadLocalRandom aus Random überschreibt und protected belässt.

null-Prüfungen mit eingebauter Ausnahmebehandlung

Bei den vorangehenden Methoden wird null als Sonderfall behandelt, und Ausnahmen vermieden. So sind etwa Objects.toString(null) oder Objects.hashCode(null) in Ordnung und es wird um null „herumgearbeitet.“ Das ist nicht immer sinnvoll, denn traditionell gilt es null als Argument und in den Rückgaben zu vermeiden. Es ist daher gut, als erstes in einem Methodenrumpf zu testen, ob die Argumente ungleich null sind – es sei denn, das ist unbedingt gewünscht.

Für diese Tests, dass Referenzen ungleich null sind, bietet Objects ein paar requireNonNull(…)-Methoden, die null-Prüfungen übernehmen und im Fehlerfall eine NullPointerException auslösen. Diese Tests sind dann praktisch bei Konstruktoren oder Settern, die Werte initialisieren sollen, aber verhindern möchten, dass null durchgeleitet wird.

Beispiel

Die Methode setName(…) soll kein name-Argument gleich null erlauben:

public void setName( String name ) {
this.name = Objects.requireNonNull( name );
}

Alternativ ist eine Fehlermeldung möglich:

public void setName( String name ) {
this.name = Objects.requireNonNull( name, "Name darf nicht null sein!" );
}

class java.util.Objects

§ static <T> T n requireNonNull(T obj)
Löst eine NullPointerException aus, wenn obj gleich null ist. Sonst liefert sie obj als Rückgabe. Die Deklaration ist generisch und so zu verstehen, dass der Parametertyp gleich dem Rückgabetyp ist.

§ static <T> T requireNonNull(T obj, String message)
Wie requireNonNull(obj), nur dass die Meldung der NullPointerException bestimmt wird.

§ static <T> T requireNonNull(T obj, Supplier<String> messageSupplier)
Wie requireNonNull(obj, message), nur kommt die Meldung aus dem messageSupplier. Das ist praktisch für Nachrichten, deren Aufbau teurer ist, denn der Supplier schiebt die Kosten für die Erstellung des Strings so lange hinaus, bis es wirklich zu einer NullPointerException kommt, denn erst dann ist die Meldung nötig. Neu in Java 8.

Tests auf null

Zwei neue Methoden in Java 8 bei der Klasse Objects sind isNull(Object o) und nonNull(Object o), wohinter sich ein einfacher Test auf o == null bzw. o != null verbirgt.

class java.util.Objects

§ static boolean isNull(Object obj)

§ static boolean nonNull(Object obj)
Liefert true wenn obj gleich null bzw. nicht null ist.

Im normalen Programmcode werden Entwickler diese Methoden nicht nutzen, doch sind sie praktisch für Methodenreferenzen, sodass es dann zum Beispiel heißen kann stream.filter(Objects::nonNull).usw.

Code auf verbleibenden Elementen eines Iterators ausführen

In Java 8 ist in der Schnittstelle Iterator eine neue Default-Methode forEachRemaining(Consumer<? super E> action) eingezogen, die ein beliebiges Stückchen Code – transportiert über einen Consumer – auf jedem Element ausführt. Die Default-Methode ist ein Zweizeiler:

java.util.Iterator.java, forEachRemaining()

default void forEachRemaining( Consumer<? super E> action ) {

  Objects.requireNonNull( action );

  while ( hasNext() )

    action.accept( next() );

}

Mit Hilfe dieser Methode lässt sich eine externe Iteration über eine selbstgebaute Schleife in eine interne Iteration umbauen und Lambda-Ausdrücke machen die Implementierung der Schnittstelle kurz.

Beispiel: Gib jedes Argument der Konsoleneingabe aus:

new Scanner( System.in ).forEachRemaining( System.out::println );

 

interface java.util.Iterator<E>

  • default void forEachRemaining(Consumer<? super E> action)
    Führt action auf jedem kommenden Element des Iterators bis zum letzten Element aus.

Optional: Elemente über Iterator löschen

Die Iterator-Methode next() ist eine reine Lesemethode und verändert die darunterliegende Datenstruktur nicht. Doch bietet die Schnittstelle Iterator auch eine Methode remove(), die das zuletzt von next() geliefert Objekt aus der Datensammlung entfernen kann, für die der Iterator umgesetzt wird. Da diese Operation nicht immer Sinn ergibt – etwa bei immutable Datenstrukturen, oder wenn ein Iterator zum Beispiel Dateien Zeile für Zeile ausliest – ist sie in der API-Dokumenation als optional gekennzeichnet. Das heißt, dass ein konkreter Iterator keine Löschoperation unterstützt muss und etwa einfach nichts macht, oder eine UnsupportedOperationException auslösen könnte.

In Java 8 gab es in der Schnittstelle eine kleine Veränderung dahingehend, dass die Operation remove() sich von einer abstrakten Methode (mit Zwangsimplementierung für implementierende Klassen) zur Default-Methode wandelte, die nunmehr von sich aus eine UnsupportedOperationException auslöst.

interface java.util.Iterator<E>

  • default void remove()
    Löscht das zuletzt von next() gelieferte Objekt aus der darunterliegenden Sammlung. Die Operation muss nicht zwingend von Iteratoren angeboten werden, und löst, falls nicht anderweitig überschrieben, eine UnsupportedOperationException("remove") aus.

Mehrere Zufallszahlen generieren, Stream in Random

Sind mehrere Zufallszahlen nötig, ist eine Schleife mit wiederholten Aufrufen von nextXXX() nicht nötig; stattdessen gibt es in Random zwei Sorten von Methoden, die ein Bündel von Zufallszahlen liefern. Als erstes:

§ void nextBytes(byte[] bytes)
Füllt das Feld bytes mit Zufallsbytes auf.

Neu ab Java 8 sind drei Methoden, die einen Stream von Daten liefern:

§ LongStream longs()

§ DoubleStream doubles()

§ DoubleStream gaussians()

 

Beispiel: Liefere 10 zufällige Zahlen, die vermutlich Primzahlen sind:

LongStream stream = new Random().longs().filter( v -> BigInteger.valueOf( v ).isProbablePrime(5) );

stream.limit( 10 ).forEach( System.out::println );

Die Klasse BitSet für Bitmengen

Die Klasse BitSet ist eine platzsparende und performante Alternative zu boolean-Arrays und bietet komfortable Möglichkeiten zur bitweisen Manipulation von Daten. Beliebig viele Bits lassen sich wie in anderen dynamischen Datenstrukturen hinzufügen und verwalten. Die Methoden von BitSet lesen und modifizieren die einzelnen Bits leicht und führen Mengenoperationen durch. Auch wenn der Klassenname auf „Set“ endet, ist BitSet keine Implementierung der Set-Schnittstelle und sogar ein ein bisschen irreführend, da ein Set Elemente nur einmal enthalten kann, in BitSet aber natürlich beliebig viele Nullen und Einsen vorkommen können, in dem die Reihenfolge auch eine elementare Rolle spielt.

Ein BitSet anlegen

Ein leeres BitSet wird mit dem Standard-Konstruktor angelegt. Ein weiterer Konstruktor erlaubt eine Startgröße, die ein Vergrößern der internen Datenstruktur aufschiebt.

class java.util.BitSet
implements Cloneable, Serializable

§ BitSet()
Erzeugt ein neues BitSet-Objekt.

§ BitSet(int nbits)
Erzeugt ein BitSet mit der vorgegebenen Größe von nbits. Alle Bits sind am Anfang auf false gesetzt. Ist die Größe kleiner null, so wird eine NegativeArraySizeException ausgelöst.

Weiterhin gibt es die statischen Methoden BitSet valueOf(long[])/valueOf(byte[])/valueOf(ByteBuffer)/valueOf(LongBuffer) um Bit-Mengen aus anderen Quellen zu importieren. Es exportieren dann das BitSet mit toByteArray() in ein byte[] und toLongArray() in ein long[].

BitSet füllen und Zustände erfragen

Jedes Bit an einer Position besitzt zwei Zustände: gesetzt oder nicht gesetzt. Dies bringt es in die Nähe der booleschen Werte, die ebenso zwei Zustände besitzen. Mit zwei Methoden lassen sich die Bits des BitSet leicht ändern: set(bitPosition) und clear(bitPosition). Da der Bit-Container automatisch wachsen kann, ist es problemlos möglich, in einem BitSet-Exemplar mit 100 Bit das Bit 300 zu setzen. Das Objekt wird automatisch mit 200 false-Bits aufgefüllt, bevor das Bit 300 gesetzt wird.

Die Abfrage, ob ein Bit gesetzt ist, erfolgt mit der Methode get(bitPosition). Sie gibt true zurück, falls das Bit gesetzt ist, andernfalls false.

Beispiel: Setze in einem BitSet das erste und das dritte Bit:

BitSet bs = new BitSet();
bs.set( 0 );
bs.set( 2 );
System.out.println( bs.get(0) ); // true
System.out.println( bs.get(1) ); // false
System.out.println( bs.nextSetBit(1) ); // 2

class java.util.BitSet
implements Cloneable, Serializable

§ boolean get(int bitIndex)
Liefert den Wert des Bits am übergebenen Index. Kann bei negativem Index wieder eine IndexOutOfBoundsException auslösen.

§ BitSet get(int fromIndex, int toIndex)
Liefert ein neues BitSet-Objekt mit den ausgewählten Bits.

§ void set(int bitIndex)

§ clear(int bitIndex)
Setzt oder löscht ein Bit. Ist der Index negativ, wird eine IndexOutOfBoundsException ausgelöst.

§ void set(int bitIndex, boolean value)
Setzt den Wahrheitswert value an die Stelle bitIndex.

§ void set(int fromIndex, int toIndex)

§ clear(int fromIndex, int toIndex)
Setzt oder löscht Bits im ausgewiesenen Bereich.

§ void set(int fromIndex, int toIndex, boolean value)
Setzt den Wahrheitswert value im ausgewählten Bereich.

§ void flip(int bitIndex)
Setzt das Bit an der Stelle bitIndex auf das Komplement. Aus true wird false, und aus false wird true.

§ void flip(int fromIndex, int toIndex)
Setzt alle Bits im gegebenen Bereich auf das Komplement.

§ int nextSetBit(int fromIndex)/previousSetBit(int fromIndex)

§ int nextClearBit(int fromIndex)/previousClearBit(int fromIndex)
Liefert den Index des nächsen/vorangenden als true bzw. false gesetzten Bits ab fromIndex. Gibt es keine Position, ist die Rückgabe –1. Der Index ist inklusiv.

Mengenorientierte Operationen

Das BitSet erlaubt mengenorientierte Operationen wie Und, Oder, Xor mit einem weiteren BitSet, etwa in der Methode and(BitSet). Jedes Bit des übergebenen BitSet wird mit dem aktuellen Objekt in einer bestimmten Weise verknüpft. Das Ergebnis der Operation wird dem aktuellen Objekt zugewiesen. Wichtige Operationen sind:

§ Die Oder-Operation setzt das Bit, falls es im eigenen BitSet oder im zweiten BitSet gesetzt ist.

§ Die Und-Operation setzt das Bit, falls es im eigenen Objekt und im zweiten BitSet gesetzt ist.

§ Die Xor-Operation setzt das Bit, falls es nur in einem der beiden BitSet-Objekte gesetzt ist.

Die Operationen bilden die Basis für die Mengenvereinigung, den Durchschnitt und den symmetrischen Durchschnitt.

class java.util.BitSet
implements Cloneable, Serializable

§ void and(BitSet set)

§ void or(BitSet set)

§ void xor(BitSet set)
Verknüpft dieses BitSet-Exemplar per Und-, Oder-, Xor-Operation mit dem angegebenen BitSet-Objekt.

§ void andNot(BitSet set)
Löscht alle Bits im aktuellen BitSet, deren korrespondierendes Bit in set gesetzt ist.

§ boolean intersects(BitSet set)
Liefert true, wenn das eigene BitSet die gleichen gesetzten Bits wie set hat.

Weitere Methoden von BitSet

Über die Methode size() erfahren wir, wie viele Bits das BitSet zur internen Speicherung der Werte nutzt.[1] Die Methode length() liefert die Position des höchsten gesetzten Bits. Die Anzahl aller gesetzten Bits liefert cardinality().

Beispiel

Methoden size(), length() und cardinality() im Vergleich:

BitSet bs = BitSet.valueOf( new byte[]{ 0b011001 } );

System.out.println( bs.size() ); // 64

System.out.println( bs.length() ); // 5

System.out.println( bs.cardinality() ); // 3

Die sonstigen Methoden von BitSet sind überschaubar.

class java.util.BitSet
implements Cloneable, Serializable

§ int size()
Liefert den Platzbedarf in Bits für dieses BitSet.

§ int cardinality()
Liefert die Anzahl der Bits, die true sind.

§ int length()
Liefert die „Größe“ des BitSet, also den Index des höchsten gesetzten Bits.

§ boolean clear()
Leert den Container, indem alle Bits auf false gesetzt werden.

§ boolean isEmpty()
Liefert true, wenn keine Bits gesetzt sind.

§ boolean equals(Object o)
Vergleicht sich mit einem anderen BitSet-Objekt o.

§ IntStream stream()
Liefert einen Stream der Indexe mit gesetzten Bit, vom niedridsten zum höchsten.

Beispiel: Gib alle Postionen gesetzter Bytes aus und prüfe, ob auf allen greaden Indexen das Bit gesetzt ist:

boolean b = BitSet.valueOf( new byte[]{ 0b1010 } ).stream()

.peek( System.out::println )

.allMatch( i -> (i & 1) == 1 );

Die Ausgabe wird hier sein: 1, 3.

Implementierungsdetails

Intern legt die Implementierung von BitSet die Bit-Sammlungen in einem long-Array ab. Um die Geschwindigkeit zu optimieren, sind die Methoden der Klasse BitSet nicht synchronisiert. Greift also ein Thread auf die Daten zu, während ein anderer modifiziert, kann es zu möglichen Inkonsistenzen kommen.


[1] Es ist vergleichbar mit dem capacity()-Wert eines Vektors.