Übermorgen Abend ist mein freier Vortrag über Java 8 bei der JUG in Braunschweig. Siehe auch http://www.tutego.de/blog/javainsel/2013/04/mein-abendvortrag-java-8-features-in-braunschweig-am-13-juni/.
Autor: Christian Ullenboom
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.
Thema der Woche: Parallele Versionen im Versionsverwaltungssystem führen
Studiere aufmerksam http://nvie.com/posts/a-successful-git-branching-model/.
Was sind die Kernaussagen?
Welche Alternativen gibt es, wo sind Schwachstellen?
UncheckedIOException in Java 8
Fehler bei Ein-/Ausgabe-Operationen werden in Java traditionell über eine geprüfte Ausnahme vom Typ IOException gemeldet. Bei Frameworks ist das zum Teil etwas lästig, sodass Java 8 eine Wrapper-Klasse im Paket java.io aufgenommen hat, die eine geprüfte IOException in einer ungeprüften UncheckedIOException mantelt.
class java.io.UncheckedIOException
extends RuntimeException
§ UncheckedIOException(IOException cause)
Ummantelt cause.
§ UncheckedIOException(String message, IOException cause)
Ummantelt cause mit einer zusätzlichen Meldung.
Bisher macht die Java-Bibliothek nur an einer Stelle von diesem Ausnahmetyp Gebrauch, und das ist bei lines() der Klasse BufferedReader, damit bei der Stream-API die geprüften Ausnahmen nicht im Wege stehen.
BufferedReader#lines, Files.newBufferedReader()
Der BufferedReader bietet neben den einfachen geerbten Lese-Methoden der Oberklasse Reader zwei weitere praktische Methoden:
· String readLine(): liest eine Zeile, und liefert am Ende null, wenn die letzte Zeile erreicht wurde.
· Stream<String> lines(): Liefert einen Stream von Strings. Neu in Java 8.
So ist einfach ein Programm formuliert, welches alle Zeilen einer Datei abläuft:
try ( BufferedReader in = Files.newBufferedReader( Paths.get( "lyrics.txt" ), StandardCharsets.ISO_8859_1 ) ) {
for ( String line; (line = in.readLine()) != null; )
System.out.println( line );
}
catch ( IOException e ) {
e.printStackTrace();
}
Beispiel
Mit der Stream-API sieht es ähnlich aus; kurz skizziert:
try ( BufferedReader in = Files.newBufferedReader( … ) ) {
in.lines().forEach( System.out::println );
}
Falls es beim Lesen über den Stream zu einem Fehler kommt, wird eine RuntimeException vom Typen UncheckedIOException ausgelöst.
CharSequence-Erweiterungen seit Java 8
In Java 8 vergrößert sich die Schnittstelle um zwei Default-Methoden:
interface java.lang.CharSequence
§ default IntStream chars()
§ default IntStream codePoints()
Alle implementierenden Klassen bieten ab Java 8 diese beiden zusätzlichen Methoden. Die Bedeutung von default und Schnittstellen im Allgemeinen bleibt ein Detail aus Kapitel 6, an dieser Stelle wollen wir nur den Vorteil betonen, dass chars() gut dafür verwendet werden kann, über die Zeilen zu laufen. Allerdings hat das nichts mit dem erweiterten for zu tun, sondern mit einem anderen Programmieridiom.
Beispiel
Laufe über eine Zeichenkette und gib jedes Zeichen aus.
"Drama is life with the dull bits left out. (Hitchcock)".chars().forEach( c ->
System.out.print( (char) c )
);
Arrays.setAll(…)/parallelSetAll(…)
Neben der Möglichkeit ein Feld mit festen Werten zu füllen, sind in Java 8 noch ein paar Methoden setAll(…)/parallelSetAll(…) hinzugekommen. Die Methoden durchlaufen ein gegebenes Feld und rufen eine bestimmte Methode für jeden Index auf, die zur Initialisierung verwendet wird.
Beispiel
Fülle ein double-Feld mit Zufallszahlen:
double[] randoms = new double[10];
Arrays.setAll( randoms, v -> Math.random() );
System.out.println( Arrays.toString( randoms ));
Das Beispiel nutzt eine spezielle Syntax, die sogenannten Lambda-Ausdrücke, um die Funktion zu beschreiben.
class java.util.Arrays
- static void setAll(double[] array, IntToDoubleFunction generator)
- static void setAll(int[] array, IntUnaryOperator generator)
- static void setAll(long[] array, IntToLongFunction generator)
- static <T> void setAll(T[] array, IntFunction<? extends T> generator)
- static void parallelSetAll(double[] array, IntToDoubleFunction generator)
- static void parallelSetAll(int[] array, IntUnaryOperator generator)
- static void parallelSetAll(long[] array, IntToLongFunction generator)
- static <T> void parallelSetAll(T[] array, IntFunction<? extends T> generator)
Läuft ein gegebenes Feld komplett ab und übergibt dabei dem generator Schritt für Schritt den Index. Der Generator bildet den Index auf einen Wert ab, der wiederum zur Feldinitialisierung genutzt wird.
Arrays.parallelSort(…)
In Java 8 sind neue Sortiermethoden hinzugekommen, die für sehr große Felder geeignet sind. Bei den neuen parallelSort(…)-Methoden verwendet die Bibliothek mehrere Threads um Teile parallel zu sortieren, was die Geschwindigkeit erhöhen kann. Eine Garantie ist das aber nicht, denn ein Performance-Vorteil ergibt sich wirklich nur bei großen Feldern.
class java.util.Arrays
§ static void parallelSort(byte[] a)
§ static void parallelSort(byte[] a, int fromIndex, int toIndex)
§ static void parallelSort(char[] a)
§ static void parallelSort(char[] a, int fromIndex, int toIndex)
§ static void parallelSort(short[] a)
§ static void parallelSort(short[] a, int fromIndex, int toIndex)
§ static void parallelSort(int[] a)
§ static void parallelSort(int[] a, int fromIndex, int toIndex)
§ static void parallelSort(long[] a)
§ static void parallelSort(long[] a, int fromIndex, int toIndex)
§ static void parallelSort(float[] a)
§ static void parallelSort(float[] a, int fromIndex, int toIndex)
§ static void parallelSort(double[] a)
§ static void parallelSort(double[] a, int fromIndex, int toIndex)
§ static <T extends Comparable<? super T>> void parallelSort(T[] a)
§ static <T extends Comparable<? super T>> void parallelSort(T[] a, int fromIndex, int toIndex)
§ static <T> void parallelSort(T[] a, Comparator<? super T> c)
§ static <T> void parallelSort(T[] a, int fromIndex, int toIndex, Comparator<? super T> c)
Der verwendete Algorithmus ist einfach zu verstehen: zunächst wird das Feld ist Teilfelder partitioniert, die werden dann parallel sortiert und dann zu einem größeren sortierten Feld zusammengelegt. Das Verfahren nennt sich auch parallel sort-merge.
Na endlich sieht Java 8’s Optional-Typ besser aus
http://download.java.net/jdk8/docs/api/java/util/Optional.html:
static <T> Optional<T>
empty()
Returns an empty Optional
instance.
Indicates whether some other object is "equal to" this Optional.
If a value is present in this Optional
, returns the value, otherwise throws NoSuchElementException
.
int
hashCode()
Returns the hash code value of the present value, if any, or 0 (zero) if no value is present.
void
ifPresent(Consumer<? super T> consumer)
Have the specified consumer accept the value if a value is present, otherwise do nothing.
boolean
isPresent()
Return true
if there is a value present, otherwise false
.
static <T> Optional<T>
of(T value)
Return an Optional
with the specified present value.
Return the value if present, otherwise return other
.
T
orElseGet(Supplier<? extends T> other)
Return the value if present, otherwise invoke other
and return the result of that invocation.
<X extends Throwable>
TorElseThrow(Supplier<? extends X> exceptionSupplier)
Return the contained value, if present, otherwise throw an exception to be created by the provided supplier.
Returns a non-empty string representation of this Optional suitable for debugging.
Was mir immer noch fehlt: fromNullable(v). Arg, das fehlt.
Java und Unendlich
Der Überlauf einer mathematischen Operation führt zu einem positiven oder negativen Unendlich.
Beispiel
Multiplikation zweier wirklich großer Werte:
System.out.println( 1E300 * 1E20 ); // Infinity
System.out.println( –1E300 * 1E20 ); // -Infinity
Für die Werte deklariert die Java-Bibliothek in Double und Float zwei Konstanten; zusammen mit der größten und kleinsten darstellbaren Fließkommazahl sind das:
Wert für |
Float |
Double |
positiv unendlich |
Float.POSITIVE_INFINITY |
Double.POSITIVE_INFINITY |
negativ unendlich |
Float.NEGATIVE_INFINITY |
Double.NEGATIVE_INFINITY |
kleinster Wert |
Float.MIN_VALUE |
Double.MIN_VALUE |
größter Wert |
Float.MAX_VALUE |
Double.MAX_VALUE |
Tabelle 1.8: Spezialwerte und ihre Konstanten
Das Minimum für double-Werte liegt bei etwa 10^–324 und das Maximum bei etwa 10^308. Weiterhin deklarieren Double und Float Konstanten für MAX_EXPONENT/MIN_EXPONENT.
Hinweis
Die Anzeige des Über-/Unterlaufs und des undefinierten Ergebnisses gibt es nur bei Fließkommazahlen, nicht aber bei Ganzzahlen.
public final class java.lang.Float/Double
extends Number
implements Comparable<Float/Double>
§ static boolean isInfinite(float/double v)
Liefert true, wenn v entweder POSITIVE_INFINITY oder NEGATIVE_INFINITY ist.
§ static boolean isFinite(float/double d)
Liefert true, wenn d eine endliche Zahl ist. Neu in Java 8.
Blöcke mit Code und die funktionale Schnittstelle java.util.function.Consumer
Anweisungen von Code lassen sich in eine Methode eines Objekts setzen und auf diese Weise weitergeben. Das ist eine häufige Notwendigkeit, für die das Paket java.util.function eine einfache funktionale Schnittstelle Consumer vorgibt:
interface java.util.function.Consumer<T>
* void accept(T t). Führt Operationen mit der Übergabe t durch.
Die accept(…)-Methode bekommt ein Argument – wobei die Implementierung natürlich nicht zwingend darauf zurückgreifen muss – und liefert keine Rückgabe. Transformationen sind damit nicht möglich, denn nur über Umwege kann der Konsument die Ergebnisse speichern, und dafür ist die Schnittstelle nicht gedacht. Consumer-Typen sind eher gedacht als Endglied einer Kette, in der zum Beispiel Dateien in eine Datei geschrieben werden, die vorher verarbeitet wurden.
Immer repräsentieren Konsumenten Code, und eine API kann nun einfach einen Code-Block nach der Art doSomethingWith(myConsumer) annehmen, und ihn etwa in einem Hintergrund-Thread abzuarbeiten, wiederholend auszuführen, nach einer erlaubten Maximaldauer abbrechen, die Zeit messen, …
Beispiel
Implementiere einen Consumer-Wrapper, der die Ausführungszeit eines anderen Konsumenten loggt.
import java.util.function.*;
import java.util.logging.Logger;
class Consumers {
public static <T> Consumer<T> measuringConsumer( Consumer<T> block ) {
return t -> {
long start = System.nanoTime();
block.accept( t );
long duration = System.nanoTime() – start;
Logger.getAnonymousLogger().info( "Ausführungszeit (ms): " + duration );
};
}
}
Folgender Aufruf zeigt die Nutzung:
Consumer<Void> wrap = measuringConsumer( (Void) -> System.out.println( "Test" ) );
wrap.accept( null );
Typ Consumer in der API
In der Java-API zeigt sich der Typ Consumer in der Regel als Argument einer Methode forEach(Consumer), die Datenquellen abläuft und für jedes Element accept(…) aufruft. Interessant ist die Methode an dem Typ Iterable, denn die wichtigen Collection-Datenstrukturen wie ArrayList implementieren diese Schnittstelle.
Beispiel
Gib jedes Element einer Liste auf der Konsole aus:
Arrays.asList(1, 2, 3, 4).forEach( t -> System.out.println( t ) );
Thema der Woche: REST-Endpoint-Design
Studierte http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api und extrahiere die Kernthesen.
Prädikate und java.util.function.Predicate
Ein Prädikat ist eine Aussage über einen Gegenstand, die wahr oder falsch. Die Frage mit Character.isDigit(‚a‘), ob das Zeichen „a“ eine Ziffer ist, wird mit falsch beantwortet – isDigit ist also ein Prädikat, weil es über einen Gegenstand, einem Zeichen, eine Wahrheitsaussage fällen kann.
Flexibler sind Prädikate, wenn sie als Objekte repräsentiert werden, weil sie dann an unterschiedliche Stellen weitergegeben werden können, wenn etwa über ein Prädikat bestimmt, was aus einer Sammlung gelöscht werden soll oder ob mindestens ein Element in einer Sammlung ist, was ein Prädikat erfüllt.
Das java.util.function-Paket deklariert eine flexible funktionale Schnittstelle Predicate auf folgende Weise:
interface java.util.function.Predicate<T>
* boolean test(T t)
Führt einen Test auf t durch und liefert true, wenn das Kriterium erfüllt ist.
Beispiel
Der Test, ob ein Zeichen eine Ziffer ist, kann durch Prädikat-Objekte nun auch anders durchgeführt werden:
Predicate<Character> isDigit = (Character c) -> Character.isDigit( c );
System.out.println( isDigit.test(‚a‘) ); // false
Hätte es die Schnittstelle Predicate schon früher in Java 1.0 gegeben, hätte es einer der Methode Character.isDigit(…) gar nicht bedurft, es hätte auch ein Predicate als statische Variable in Character geben können, so dass ein Test dann geschrieben würde als Character.IS_DIGIT.test(…) oder als Rückgabe von einer Methode isDigit(), mit der Nutzung Character.isDigit().test(…). Es ist daher gut möglich, dass sich in Zukunft die API dahingehend verändert, dass Aussagen auf Gegenständen mit Wahrheitsrückgabe nicht mehr als Methoden bei den Klassen realisiert werden, sondern als Prädikat-Objekte angeboten werden. Aber Methoden-Referenzen geben zum Glück die Flexibilität, dass problemlos Methoden als Lambda-Ausdrücke genützt werden können und so kommen wir wieder von Methoden zu Funktionen.
Typ Predicate in der API
Es gibt in der Java-API vier Stellen, an denen Prediate-Objekte genutzt werden:
· Als Argument für Lösch-Methoden, um in Sammlungen Elemente zu spezifizieren, die gelöscht werden sollen.
· Bei den Default-Methoden der Predicate-Schnittstelle selbst, um Prädikate zu verknüpfen.
· Bei regulären Ausdrücken, um ein Pattern als Predicate nutzen zu können.
· In der Stream-API, bei der Objekte beim Durchlaufen des Stroms über ein Prädikat identifiziert werden, um sie etwa auszufiltern.
Beispiel
Lösche aus einer Liste mit Zeichen alle die, die Ziffern sind (es bleiben nur Zeichen übrig, etwa Buchstaben).
Predicate<Character> isDigit = (Character c) -> Character.isDigit( c );
List<Character> list = new ArrayList( Arrays.asList( ‚a‘, ‚1‘ ) );
list.removeIf( isDigit );
Default-Methoden von Predicate
Es gibt eine Reihe von Default-Methoden, die die funktionale Schnittstelle Predicate anbietet. Zusammenfassend:
interface java.util.function.Predicate<T>
default Predicate<T> negate()
default Predicate<T> and(Predicate<? super T> p)
default Predicate<T> or(Predicate<? super T> p)
default Predicate<T> xor(Predicate<? super T> p)
Die Methodennamen sprechen für sich.
Beispiel
Lösche aus einer Liste mit Zeichen alle die, die keine Ziffern sind.
Predicate<Character> isDigit = (Character c) -> Character.isDigit( c );
Predicate<Character> isNotDigit = isDigit.negate();
List<Character> list = new ArrayList( Arrays.asList( ‚a‘, ‚1‘ ) );
list.removeIf( isNotDigit );
Funktionale Programmierung in Java am Beispiel vom Comparator
Funktionale Programmierung hat auch daher etwas akademisches, weil in den Köpfen der Entwickler oftmals dieses Programmierparadigma nur mit mathematischen Funktionen in Verbindung gebracht wird. Und die wenigsten werden tatsächlich Fakultät oder Fibonacci-Zahlen in Programmen benötigen und daher schnell funktionale Programmierung beiseite legen. Doch diese Vorurteile sind unbegründet, und es ist hilfreich, gedanklich funktionale Programmierung von der Mathematik lösen, denn die allermeisten Programme haben nichts mit mathematischen Funktionen im eigentlichen Sinne zu tun.
Betrachten wir die Sortierung von Strings. Ein Comparator ist eine einfache Funktion, mit zwei Parametern und einer Rückgabe. Diese Funktion wiederum wird an die sort(…)-Methode übergeben. Alles das ist funktionale Programmierung, denn wir programmieren Funktionen und übergeben sie. Drei Beispiele (Generics ausgelassen):
Code |
Bedeutung |
Comparator c = (c1, c2) -> … |
Implementiert eine Funktion |
Arrays.sort(T[] a, Comparator c) |
Nimmt eine Funktion als Argument an |
Collections.reverseOrder(Comparator cmp) |
Nimmt eine Funktion an und liefert auch eine zurück |
Funktionen selbst können in Java nicht übergeben werden, also helfen sich Java-Entwickler mit der Möglichkeit, die Funktionalität in eine Methode zu kapseln, sodass die Funktion zum Objekt mit einer Methode wird, was die Logik realisiert. Lambda-Ausdrücke bzw. Methoden/Konstruktor-Referenzen geben eine kompakte Syntax.
Der Typ Comparator ist eine funktionale Schnittstelle und steht für eine besondere Funktion mit zwei Parametern gleichen Typs und einer Ganzzahl-Rückgabe. Es gibt weitere funktionale Schnittstellen, die etwas flexibler sind als Comparator, in der Weise, dass etwa die Rückgabe statt int auch double oder etwas anderes sein können.