Manche GlassFish Versionen sind commercial supported, aber nur die alten
Openstreetmap Karte in JavaFX einbinden
JavaFX and OSM (OpenStreetMap)“ http://feedly.com/k/Kx5ZZc
Guava 16.0
Guava 16.0 is now available on Maven Central! You can install it using Mave… [link]“ http://feedly.com/e/cCsIP0HO
Link: sun.misc.Unsafe and off heap memory
LongAccumulator und DoubleAccumulator
Die beiden XXXAdder-Klassen haben eine ganz spezielle Aufgabe und das ist Werte zu addieren und aufzusummieren. Allerdings gibt es noch viele weitere Aufgaben, die ähnlich wie die Summation auf ein Endergebnis gebracht werden. Für diesen Anwendungsfall deklariert das Paket java.util.concurrent.atomic weiterhin LongAccumulator und DoubleAccumulator. Im Konstruktor nehmen die Klassen einen XXXBinaryOperator an und die Identität, die beim binären Operator auf nur einem Ergebnis genau das Ergebnis gibt.
· LongAccumulator(LongBinaryOperator accumulatorFunction, long identity)
· DoubleAccumulator(DoubleBinaryOperator accumulatorFunction, double identity)
Die Methoden heißen dann accumulate(long x) bzw. accumulate(double x) und get() verdichtet das Ergebnis zum Schluss. Die Addition der Klassen LongAdder und DoubleAdder lässt sich dann alternativ ausdrücken durch new XXXXAccumulator((x, y) -> x + y, 0).
java.util.concurrent.atomic.LongAdder und DoubleAdder in Java 8
Die AtomicXXX-Klassen sind gut, wenn es nicht zu viele parallel Threads gibt, die gleichzeitig die AtomicXXX-Exemplare verändern. Der Grund ist einfach: jeder Thread warten muss, bis ein anderer Thread die Veränderung am AtomicXXX vorgenommen hat. Stehen also 100 Threads in der Schlage den AtomicXXX zu verändern, werden sie erst nacheinander abgearbeitet – das geht zwar an schnell, dennoch führt die sequenzielle Verarbeitung zu Wartesituationen.
Wenn es wirkliche viel nebenläufige Threads gibt, sind die AtomicXXX-Klassen nicht optimal und Java biete ab Java 8 zwei neue Klassen LongAdder und DoubleAdder. Ein XXXAdder sieht nach außen wie ein long/double aus (die Klassen erweitern auch Number), doch intern sind sie vielmehr eine Liste von Werten, auf die dann unterschiedliche Thread zugreifen können, ohne zu warten. Um sich das vorzustellen zu können ein Beispiel: Nehmen wir an, mehrere Threads teilen sich einen LongAdder. Ruft ein Thread add(1) auf, so führt das intern zu einem Element in einer Liste[1]. Kommt gleichzeitig add(2) am LongAdder an, muss der Thread nicht auf das Ende vom ersten add(…) warten, sondern fügt einen neuen Knoten an. Kommt später ein dritter und vierter Thread über den Weg und führt add(3) und add(4) aus, können diese ohne Warten an den ersten und zweiten existieren Knoten gehen und die Werte addieren, in den beiden internen Knoten stehen also 4 und 6. Es sind also nur so viele Knoten intern nötig, wie wirklich parallele Threads auftauchen. Eine Summation am Ende mit sum() läuft dann über die internen Knoten und summiert sie auf zu 10, was 1+2+3+4 ist.
[1] Genau genommen beim ersten Element noch in einer Variablen, die Liste beginnt erst beim zweiten Element, also beim zweiten parallelen Thread.
map(…) Methoden von Optional für funktionale Programmierung
Java 8 bekommt eine Optional Klasse und die beiden XXXmap(…)-Methoden sind besonders interessant. Sie ermöglichen einen ganz neuen Programmierstil. Warum soll ein Beispiel zeigen.
Der folgende Zweizeiler gibt auf meinem System „MICROSOFT KERNELDEBUGGER-NETZWERKADAPTER“ aus:
String s = NetworkInterface.getByIndex( 2 ).getDisplayName().toUpperCase();
System.out.println( s );
Allerdings ist der Programmcode alles andere als gut, denn NetworkInterface.getByIndex(int) kann null zurückgeben und getDisplayName() auch. Um ohne eine NullPointerException um die Klippen zu schiffen müssen wir schreiben:
NetworkInterface networkInterface = NetworkInterface.getByIndex( 2 );
if ( networkInterface != null ) {
String displayName = networkInterface.getDisplayName();
if ( displayName != null )
System.out.println( displayName.toUpperCase() );
}
Von der Eleganz des Zweizeilers ist nicht mehr viel geblieben. Integrieren wir Optional (was ja eigentlich ein toller Rückgabetyp für getByIndex() und getDisplayName():
Optional<NetworkInterface> networkInterface = Optional.ofNullable( NetworkInterface.getByIndex( 2 ) );
if ( networkInterface.isPresent() ) {
Optional<String> name = Optional.ofNullable( networkInterface.get().getDisplayName() );
if ( name.isPresent() )
System.out.println( name.get().toUpperCase() );
}
Mit Optional wird es nicht sofort besser, doch statt if können wir ein Lambda-Ausdruck einsetzen und bei ifPresent(…) einsetzen:
Optional<NetworkInterface> networkInterface = Optional.ofNullable( NetworkInterface.getByIndex( 2 ) );
networkInterface.ifPresent( ni -> {
Optional<String> displayName = Optional.ofNullable( ni.getDisplayName() );
displayName.ifPresent( name -> {
System.out.println( name.get().toUpperCase() );
} );
} );
Wenn wir nun die lokale Variablen entfernen, kommen wir aus bei:
Optional.ofNullable( NetworkInterface.getByIndex( 2 ) ).ifPresent( ni -> {
Optional.ofNullable( ni.getDisplayName() ).ifPresent( name -> {
System.out.println( name.get().toUpperCase() );
} );
} );
Von der Struktur ist das mit der if-Afrage identisch und über die Einrückungen auch zu erkennen. Fallunterscheidungen mit Optional und ifPresent(…) umzuschreiben bringt also keinen Vorteil.
In Fallunterscheidungen zu denken hilft hier nicht weiter. Was wir uns bei NetworkInterface.getByIndex( 2 ).getDisplayName().toUpperCase() vor Augen halten müssen ist eine Kette von Abbildungen. NetworkInterface.getByIndex(int) bildet auf NetworkInterface ab, getDisplayName() von NetworkInterface bildet auf String ab, und toUpperCase()bildet von einem String auf einen anderen String ab. Wir verketten also drei Abbildungen und müssten ausdrücken können: Wenn eine Abbildung fehlschlägt, dann höre mit der Abbildung auf. Und genau hier kommt Optional und map(…) ins Spiel. In Code:
Optional<String> s = Optional.ofNullable( NetworkInterface.getByIndex( 2 ) )
. map( ni -> ni.getDisplayName() )
. map( name -> name.toUpperCase() );
s.ifPresent( System.out::println );
Die Klasse Optional hilft uns bei zwei Dingen: Erstes wird map(…) beim Empfangen einer null-Referenz auf ein Optional.empty() abbilden. Und zweitens ist das Verketten von leeren Optionals kein Problem, es passiert einfach nichts – Optional.empty().map(…) führt nichts aus und die Rückgabe ist einfach nur ein leeres Optional.
Umgeschrieben mit Methoden-Referenzen und weiter verkürzt ist das Code sehr gut lesbar.
Optional.ofNullable( NetworkInterface.getByIndex( 2 ) )
. map( NetworkInterface::getDisplayName )
. map( String::toUpperCase )
.ifPresent( System.out::println );
Die Logik kommt ohne externe Fallunterscheidungen aus und arbeitet nur mit optionalen Abbildungen. Das ist ein schönes Beispiel für funktionale Programmierung.
Compact-Profile in Java 8
Nicht jedes Java-Programm braucht den vollen Satz von 4000 Typen, sondern oftmals reicht eine kleine Teilmenge. Eine Teilmenge der Java-Bibliothek wiederum ermöglicht es, kleinere Laufzeitumgebung zu bauen, was es erlaubt, Java auch für Geräte mit weniger Ressourcen einzusetzen. Eine kleine Java-Version für Embedded-Systeme braucht kein CORBA, AWT oder JavaFX und kann so viel kleiner als das Standard-JRE sein.
In Java 8[1] wurde die Bibliothek in 4 Gruppen eingeteilt, auf der einen Seite stehen drei kompakte aufsteigende Teilmengen der Standardbibliothek, genannt Compact-Profile, und auf der anderen Seite das vollständige System. Das Profil contact1 ist das kleinste, compact2 enthält contact1, contact3 enthält compact2 und das Gesamt-JRE compact3.
Welche Typen zu welchem Profil gehören dokumentiert die Java-API auf der Startseite sowie gibt jeder Typ in der Javadoc sein Profil an; die grobe Einteilung ist:
Profil |
Größe |
Pakete |
compact1 |
10 MiB |
java.io, java.math, java.net, java.nio, java.security, java.time, java.util (inklusive Stream-API), javax.crypto, javax.script, javax.security |
compact2 |
17 MiB |
java.sql, java.rmi, javax.rmi, javax.transaction, javax.xml, org.xml, org.w3c |
compact3 |
24 MiB |
java.lang.instrument, java.lang.management, java.util.prefs, java.lang.model, javax.management, javax.naming, javax.sql.rowset, javax.tools, javax.xml.crypto, org.ieft.jgss, Kerberos, SASL |
JRE/JDK |
140 MiB |
Alles weitere: java.beans, AWT, Swing, JavaFX, CORBA, java.awt.print, Sound, SOAP, Web-Service, … |
Inhalt der verschiedenen Profile mit Speicherbedarf[2]
Weiterhin ist die Anzahl verschiedener Provider minimiert worden, es gilt zum Beispiel nur Englisch als verpflichtende Sprache.
Werkzeug-Unterstützung für Profile
Die Werkzeuge javac, javadoc und jdeps aus dem JDK sind für die Profile aktualisiert worden, etwa dass sie prüfen können, ob ein Typ/Eigenschaft zum Profil gehört oder nicht. Der Schalter –profile gibt dabei das gewünschte Profil an.
Beispiel
Versuche die Klasse T mit der Deklaration class T extends java.awt.Point {} mit dem Profile compact3 zu übersetzen:
$ javac -profile compact3 T.java
T.java:2: error: Point is not available in profile ‚compact3‘
class T extends java.awt.Point { }
^
1 error
Obwohl Point eine nützliche Klasse ist, und keine plattformspezifischen Eigenschaften hat, ist das gesamte Paket java.awt gesperrt und kein Teil vom Compact-Profile.
[1] Beschrieben erstmalig unter http://openjdk.java.net/jeps/161
[2] Zahlen von JavaOne Konferenz 2013.
Annotation jdk.Exported
Im Endeffekt haben Entwickler es zu tun mit
1. der offiziellen Java-API,
2. der API aus JSR-Erweiterungen, wie der Java Enterprise API und
3. nicht-offiziellen Bibliotheken, wie quelloffenen Lösungen etwa zum Zugriff auf PDF-Dateien oder Bankautomaten.
Allerdings gibt es noch weitere Typen, die nicht im java bzw. javax-Paket liegen, die von jeder Java SE-Implementierung unterstützt werden müssen. Dazu zählen
· HTTP Server API (com.sun.net.httpserver)
· Java Debug Interface (com.sun.jdi)
· Attach API (com.sun.tools.attach)
· SCTP API (com.sun.nio.sctp)
· Management Extensions (com.sun.management)
· JConsole Plugin API (com.sun.tools.jconsole)
und ein paar Typen aus dem Sicherheits-Paket, com.sun.security.auth und com.sun.security.jgss.
Um zugängliche öffentliche bzw. protected Typen und Eigenschaften zu markieren, tragen sie eine spezielle Annotation @jdk.Exported – am dem Paket jdk lässt sich schon ablesen, dass die Annotation selbst schon sehr speziell ist, und auch nicht zur Standard-Bibliothek gehört. Alternative Java SE-Implementierungen müssen diese Typen also bereitstellen, da jedoch Oracle mit dem JDK (beziehungsweise das OpenJDK) so präsent sind, ist diese Markierung eher etwas für die Plattformentwickler, weniger für die normalen Entwickler.
Kollisionen und Hash-Funktionen
Die Wahl der richtigen Hash-Funktion ist wichtig für die Performance. Denn eine »dumme« Hash-Funktion, die beispielsweise alle Schlüssel nur auf einen konstanten Wert abbildet, erreicht keine Verteilung, sondern lediglich eine lange Liste von Schlüssel-Werte-Paaren; diese Anhäufung an einer Stelle nennt sich Clustering. Doch auch bei der besten Verteilung über N Buckets ist nach dem Einfügen des Elements N + 1 irgendwo eine Liste mit mindestens zwei Elementen aufgebaut, daher vergrößert die Bibliothek standardmäßig das Feld. Ist aber die Hash-Funktion aber so schlecht, dass alles auf das gleiche Bucket abgebildet wird, hilft dieses Re-Hashing auch nicht.
Je länger die Datenstruktur der miteinander kollidierenden Einträge wird, desto langsamer wird der Zugriff der auf Hashing basierenden Datenstruktur insgesamt. Java basiert beim Hashing einzig auf der hashCode()-Methode, und es liegt in unserer Hand sie gut zu implementieren. Die Klasse String hat eine relativ einfache (dafür schnelle) Implementierung von hashCode(), die in der Vergangenheit zu Denial of Service Attacken führte gerade weil es zu Kollisionen kam.[1]
[1] In Java 7 wurde dafür in String eine zusätzliche Hash-Methode realisiert, doch in Java 8 diese Implementierung wieder entfernt und die Implementierung für das Hashing insgesamt verändert, zumindest für die aktuellen Klassen, das ältere java.util.Hashtable blieb unverändert.
Schnittstelle Map.Entry und Updates in Java 8
Während keySet() nur die eindeutigen Schlüssel in einer Menge liefert und die assoziierten Elemente in einem zweiten Schritt geholt werden müssten, gibt entrySet() ein Set von Elementen typisiert mit Map.Entry zurück. Entry ist eine innere Schnittstelle von Map, die eine API zum Zugriff auf Schlüssel-Werte-Paare deklariert. Die wichtigen Operationen dieser Schnittstelle sind getKey(), getValue() und setValue(), wobei die letzte Methode optional ist, aber etwa von HashMap angeboten wird. Neben diesen Methoden überschreibt Entry auch hashCode() und equals(…).
Beispiel: Laufe die Elemente HashMap als Menge von Map.Entry-Objekten ab:
for ( Map.Entry<String, String> e : h.entrySet() )
System.out.println( e.getKey() + „=“ + e.getValue() );
Map.Entry ist eher ein Interna und die Objekte dienen nicht der langfristen Speicherung. Ein entrySet() ist eine Momentaufnahme und das Ergebnis sollte nicht referenziert werden, denn ändert sich der darunterliegende Assoziativspeicher, ändern sich auch die Entry-Objekt und das Set<Map.Entry> als ganzes ist vielleicht nicht mehr gültig. Entry-Objekt sind nur gültig im Moment der Iteration, was den Nutzen eingeschränkt. Daher ist die Rückgabe von entrySet() mit Set<Map.Entry<K,V>> auch relativ unspezifisch, um was für eine Art von Set es sich genau handelt; ob HashSet oder vielleicht NavigableSet spielt keine Rolle.
Auch wenn die Map.Entry-Objekte nicht für die Speicherung gedacht sind, können Sie in Java 8 in einem Strom von Daten verarbeitet und in einer zustandsbehafteten Operation sortiert werden. Der Bonus der Entry-Objekte im Strom ist einfach, dass es von Vorteil ist, Schüssel und Wert in einem Objekte gekapselt zu sehen. Aber was ist, wenn jetzt der Strom sortiert werden soll, etwa nach dem Schlüssel, oder dem Wert? Hier kommen neue Methoden von Java 8 ins Spiel, die den nötigen Comparator liefern.
Beispiel: Erfrage eine Menge von Entry-Objekten und sortiere sie nach dem assoziierten Wert:
map.entrySet()
.stream()
.sorted( Map.Entry.<String, String>comparingByValue() )
.forEach( System.out::println );
static interface Map.Entry<K,V>
§ static <K extends Comparable<? super K>,V> Comparator<Map.Entry<K,V>> comparingByKey()
§ static <K,V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue()
§ static <K,V> Comparator<Map.Entry<K,V>> comparingByKey(Comparator<? super K> cmp)
§ static <K,V> Comparator<Map.Entry<K,V>> comparingByValue(Comparator<? super V> cmp)
String-Repräsentation, Gleichheitstest, Hashwert und Klon eines Assoziativspeicher
toString() auf Assoziativspeichern liefert eine Zeichenkette, die den Inhalt der Sammlung aufzeigt. Die Stringrepräsentation liefert jeden enthaltenen Schlüssel, gefolgt von einem Gleichheitszeichen und dem zugehörigen Wert. Entwickler sollten nie diese Zeichenkennung parsen bzw. irgendwelche Annahmen über die Formatierung machen.
Beispiel: Ein Assoziativspeicher soll die Zahlen 1, 2, 3 jeweils mit ihrem Quadrat assoziieren. Zum Aufbau benutzen wir eine fortgeschrittene Technik aus Java 8.
Map<Integer, Integer> map = Arrays.asList( 1, 2, 3 )
.stream()
.collect( Collectors.<Integer, Integer, Integer>toMap( id -> id, id -> id*id) );
System.out.println( map/*.toString*/ ); // {1=1, 2=4, 3=9}
Aus Object überschreiben die Standardimplementierungen die Methoden equals(…) und hashCode().
Die Klassen HashMap (und Unterklasse LinkedHashMap), IdentityHashMap, TreeMap, ConcurrentSkipListMap und EnumMap deklarieren eine öffentliche clone()-Methode, die eine Kopie eines Assoziativspeichers erzeugt. Die Kopie bezieht sich allerdings nur auf den Assoziativspeicher selbst; die Schlüssel- und Wert-Objekte teilen sich Original und Klon. Diese Form der Kopie nennt sich auch flache Kopie (engl. shallow copy). Eine Veränderung an den enthaltenen Schlüssel-Werte-Objekten betrifft also immer beide Datenstrukturen, und eine unsachgemäße Modifikation kann zu Unregelmäßigkeiten im Original führen. Eine ConcurrentHashMap oder WeakHashMap unterstützt kein clone(), und eigentlich ist clone() überhaupt nicht nötig, denn die Konstrukturen der Datenstrukturen können immer eine andere Datenstruktur als Vorlage nehmen, etwa clone = new ConcurrentHashMap(existingMap).
Map-Operationen in Abhängigkeit von (nicht-)existierenden Werten in Java 8
Die Map-API hat seit Java 8 einige clevere Methoden, die mehrere Operationen zusammenfassen, wobei die Funktionsweise folgendem Bauplan entspricht: ist ein assoziierter Wert zu einem Schlüssel (nicht) vorhanden, dann tue dies, sonst das.
interface java.util.Map<K,V>
§ default V putIfAbsent(K key, V value)
Testet zuerst, ob es zu dem gegeben Schlüssel key einen assoziierten Wert existiert und wenn ja, gibt es keine Änderung an der Datenstruktur, nur der alte assoziierte Wert wird zurückgegeben. Existiert kein assoziierter Wert, speichert die Datenstruktur zum Schlüssel den value. Die Rückgabe von putIfAbsent(…) ist null, falls es vorher keinen alten assoziierten Wert gab, andernfalls die Referenz vom alten Objekt (was auch null ein kann, wenn die Map auch null-Werte erlaubt), was jetzt durch den neuen Wert überschreiben wurde. Falls null als Wert in der Map erlaubt ist – wie etwa in HashMap – so gilt eine Besonderheit: ist ein existierender Schlüssel mit null assoziiert, dann würde putIfAbsent(…) den Wert null mit etwas anderem überschreiben.
§ default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
Vergleichbar mit putIfAbsent(…), nur nutzt diese Methode eine Berechnungsmethode statt einen festen Wert. Ein wichtiger Punkt ist, dass wenn die Berechnungsfunktion null zurückgibt, nichts an dem Assoziativspeicher verändert wird und der alte Wert bleibt. Der Rückgabewert ist immer entweder der letzte assoziierter Wert oder der neue Eintrag, es sein denn, die mappingFunction lieferte null. Die Methode lässt sich damit perfekt in einer Methodenkaskadierung verwenden der Art map.computeIfAbsent(…).methodeVonV(…).
§ default V computeIfPresent(K key, BiFunction<? super K, ? super V,? extends V> remappingFunction)
Überschreibt den assoziierten Wert mit einem von der Funktion berechneten neuen Wert, wenn das Schlüssel/Werte-Paar existiert. Dann liefert die Methode denen neuen Wert zurück. Zwei Sonderfälle sind zu unterscheiden. Falls es zu dem Schlüssel key keinen Wert gibt macht computeIfPresent(…) nichts und die Rückgabe ist null. Gibt es einen assoziierten Wert, doch die auf den Wert angewendete Funktion liefert null, wird das Schlüssel/Werte-Paar gelöscht und die Rückgabe ist ebenfalls null.
Beispiel. Java bietet von Haus aus keine Datenstruktur, die wie ein Assoziativspeicher arbeitetet, aber einen Schlüssel mit einer Sammlung von Werten assoziieren kann. Doch so eine Klasse ist schnell geschrieben:
class Multimap<K, V> {
private final Map<K, Collection<V>> map = new HashMap<>();
public Collection<V> get( K key ) {
return map.getOrDefault( key, Collections.<V> emptyList() );
}
public void put( K key, V value ) {
map.computeIfAbsent( key, k -> new ArrayList<>() )
.add( value );
}
}
Ein kleines Beispiel:
Multimap<Integer, String> mm = new Multimap<>();
System.out.println( mm.get( 1 ) ); // []
mm.put( 1, „eins“ );
System.out.println( mm.get( 1 ) ); // [eins]
mm.put( 1, „one“ );
System.out.println( mm.get( 1 ) ); // [eins, one]
interface java.util.Map<K,V>
§ default V merge(K key, V value, BiFunction<? super V, ? super V,? extends V> remappingFunction)
Setzt einen neuen Eintrag in die Map oder verschmilzt einen existierenden Eintrag mit der angegebenen Funktion. Von der Semantik her ist das die komplexeste Methode. Der erste Fall einfach, denn wenn es zum Schlüssel kein Wert gibt; dann wird das Schlüssel-/Werte Paar in die Map gesetzt und merge(…) liefert als Rückgabe den value. Gibt es schon einen assoziierten Wert, wird die Funktion mit dem alten Wert und value auf aufgerufen (eine BiFunction hat zwei Parameter) und der alte assoziierte Wert mit diesem neuen Wert überschrieben und die Rückgabe von merge(…) liefert diesen neuen Wert. Jetzt gibt es noch zwei Sonderfälle, und die hängen damit zusammen wenn das Argument value gleich null oder die Funktion null liefert. In beiden Fällen wird das Schlüssel/Wert-Paar gelöscht und die Rückgabe von merge(…) ist null.
Beispiel: Zu einer ID soll ein Punkt assoziiert werden. Neue hinzugefügte Punkte zu dieser ID sollen die Koordinate des ursprünglichen Punktes verschieben.
Map<Integer, Point> map = new HashMap<>();
BiFunction<? super Point, ? super Point, ? extends Point> remappingFunc =
(oldVal, val) -> { val.translate( oldVal.x, oldVal.y ); return val; };
map.merge( 1, new Point( 12, 3 ), remappingFunc );
System.out.println( map.get( 1 ) ); // java.awt.Point[x=12,y=3]
map.merge( 1, new Point( -2, 2 ), remappingFunc );
System.out.println( map.get( 1 ) ); // java.awt.Point[x=10,y=5]
map.merge( 1, new Point( 0, 5 ), remappingFunct );
System.out.println( map.get( 1 ) ); // java.awt.Point[x=10,y=10]
Strom von Zufallszahlen generieren
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 Methoden, die einen Stream von Zufallszahlen liefern:
§ IntStream ints(…)
§ LongStream longs(…)
§ DoubleStream doubles(…)
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 Methoden ints(…), longs(…) und doubles(…) gibt es in drei Spielarten.
Parametrisierung |
Erklärung |
IntSteam ints() |
Liefert unendlichen Strom von Zufallszahlen im kompletten Wertebereich der Primitiven |
LongStream longs() |
|
DoubleStream doubles() |
|
ints(long streamSize) |
Liefert einen Strom mit streamSize Zufallszahlen |
longs(long streamSize) |
|
double(long streamSize) |
|
ints(int randomNumberOrigin, int randomNumberBound) longs(long randomNumberOrigin, long randomNumberBound) doubles(double randomNumberOrigin, double randomNumberBound) |
Liefert einen unendlichen Strom vom Zufallszahlen mit Werten im Bereich randomNumberOrigin (inklusiv) bis randomNumberBound (exklusiv) |
ints(int randomNumberOrigin, int randomNumberBound) |
|
longs(long randomNumberOrigin, long randomNumberBound) |
|
doubles(double randomNumberOrigin, double randomNumberBound) |
|
ints(long streamSize, int randomNumberOrigin, int randomNumberBound) |
Liefert einen Strom mit streamSize Zufallszahlen an der Zahl mit Werten im Bereich randomNumberOrigin (inklusiv) bis randomNumberBound (exklusiv) |
longs(long streamSize, long randomNumberOrigin, long randomNumberBound) |
|
doubles(long streamSize, double randomNumberOrigin, double randomNumberBound) |
Stream-Methoden der Random-Klasse
Beispiel: Gib 5 Fließkomma-Zufallszahlen im Bereich von 10 bis 20 aus.
new Random().doubles(5, 10, 20).forEach( System.out::println );
Beispiel ListResourceBundle
Ein Resource-Bundle ohne Dateien, realisiert als ListResourceBundle, kann so aussehen:
com/tutego/insel/bundle/MonthResourceBundle_de_DE.java, MonthResourceBundle_de_DE
public class MonthResourceBundle_de_DE extends ListResourceBundle {
private static final String[] MONTHS = {
„Jan“, „Feb“, „Mrz“, „Apr“, „Mai“, „Jun“, „Jul“, „Aug“, „Sep“, „Okt“, „Nov“, „Dez“
};
private static final Object[][] contents = {
{ „jan“, MONTHS[0] },
{ „month“, MONTHS }
};
@Override
protected Object[][] getContents() {
return contents;
}
}
Die Nutzung der Klasse ist wie folgt:
com/tutego/insel/bundle/MonthResourceBundleDemo.java, main()
ResourceBundle bundle = ResourceBundle.getBundle( „com.tutego.insel.bundle.MonthResourceBundle“ );
System.out.println( bundle.getString( „jan“ ) ); // Jan
System.out.println( Arrays.toString( bundle.getStringArray( „month“ ) ) ); // [Jan, Feb, …
System.out.println( Collections.list( bundle.getKeys() ) ); // [month, jan]
In diesem Fall lädt ResourceBundle.getBundle(…) keine Property-Datei, sondern eine Klasse im Klassenpfad. Die API von ResourceBundle bietet auch eine Methode getStringArray(), die ein Feld zurückgibt, jedoch ist das bei Ressourcen-Dateien nicht nötig, nur bei Ressourcen, die „von Hand“ programmiert wurden, wie in unserem Beispiel.
Parallele Berechnung von Präfixen über Arrays-Klasse in Java 8
Stehen mehrere Prozessoren bzw. Kerne zur Verfügung können einige Berechnungen bei Feldern parallelisiert werden. Eine Algorithmus nennt sich parallele Präfix-Berechnung und basiert auf der der Idee, dass eine assoziative Funktion – nennen wir sie f – auf eine bestimmte Art und Weise auf Elemente eines Feldes – nennen wir es a – angewendet wird, nämlich so:
· a[0]
· f(a[0], a[1])
· f(a[0], f(a[1], a[2]))
· f(a[0], f(a[1], f(a[2], a[3])))
· …
· f(a[0], f(a[1], … f(a[n-2], a[n-1])…))
In der Aufzählung sieht das etwas verwirrend aus, daher soll ein praktisches Beispiel zum Verständnis anregen. Das Feld sei [1, 3, 0, 4] und die binäre Funktion die Addition.
Index |
Funktion |
Ergebnis |
0 |
a[0] |
1 |
1 |
a[0] + a[1] |
1 + 3 = 4 |
2 |
a[0] + (a[1] + a[2]) |
1 + (3+0) = 4 |
3 |
a[0] + (a[1] + (a[2] + a[3])) |
1 + (3+(0+4)) = 8 |
Präfix-Berechnung vom Feld [1, 3, 0, 4] mit Additions-Funktion
Auf den ersten Blick wirkt das wenig spannend, doch kann der Algorithmus parallelisiert werden und somit im Besten Fall in logarithmischer Zeit (mit n Prozessoren) gelöst werden. Voraussetzung dafür ist allerdings eine assoziative Funktion, wie Summe, Maximum, … Ohne genau ins Detail zu gehen könnten wir uns vorstellen, dass ein Prozessor/Kern 0+4 berechnet, ein anderer zeitgleich 1+3, und dann das Ergebnis zusammengezählt wird.
Beispiel. Das Beispiel unserer Präfix-Berechnung mit Hilfe einer Methode aus Arrays:
int[] array = {1, 3, 0, 4};
Arrays.parallelPrefix( array, (a, b) -> a + b );
System.out.println( Arrays.toString( array ) ); // [1, 4, 4, 8]
Die Implementierung nutzt eine fortgeschrittene Syntax aus Java 8, die Lambda-Ausdrücke. statt (a + b) -> a + b kann es sogar mit Integer::sum noch verkürzt werden.
Ein weiteres Beispiel: Finde das Maximum in einer Menge von Fließkommazahlen:
double[] array = {4.8, 12.4, -0.7, 3.8 };
Arrays.parallelPrefix( array, Double::max );
System.out.println( array[array.length -1 ] ); // 12.4
Das Beispiel nutzt schon die Methode, die Arrays für die parallele Präfix-Berechnung bietet:
class java.util.Arrays
§ static void parallelPrefix(int[] array, IntBinaryOperator op)
§ static void parallelPrefix(int[] array, int fromIndex, int toIndex, IntBinaryOperator op)
§ static void parallelPrefix(long[] array, LongBinaryOperator op)
§ static void parallelPrefix(long[] array, int fromIndex, int toIndex, LongBinaryOperator op)
§ static void parallelPrefix(double[] array, DoubleBinaryOperator op)
§ static void parallelPrefix(double[] array, int fromIndex, int toIndex, DoubleBinaryOperator op)
§ static <T> void parallelPrefix(T[] array, BinaryOperator<T> op)
§ static <T> void parallelPrefix(T[] array, int fromIndex, int toIndex, BinaryOperator<T> op)
Internationalisierung von Log-Methoden mit setResourceBundle(…) und logrb(…)
Nutzer von log(…) und logp(…) können die Meldungen internationalisieren. Dafür bietet die API zwei Möglichkeiten. Als erstes kann seit Java 8 global für den Logger mit setResourceBundle(ResourceBundle bundle) ein ResourceBundle zugewiesen werden. Immer dann, wenn eine Log-Nachricht geschrieben wird, wird der Logger zunächst die Nachricht als Schlüssel in der Ressourcen-Abbildung nutzen; gibt es zu dem Schlüssel keine Übersetzung, gilt die Nachricht als Log-Ausgabe.
Neben dieser globalen Zuweisung über setResourceBundle(…) gibt es zwei Extra-Methoden logrb(…), die ResourceBundle-Objekte direkt annehmen:
· void logrb(Level level, String sourceClass, String sourceMethod, ResourceBundle bundle, String msg, Object… params)
· void logrb(Level level, String sourceClass, String sourceMethod, ResourceBundle bundle, String msg, Throwable thrown)
Beispiel: Die Log-Meldung nimmt logrb(…) also aus einem ResourceBundle und das kann so aussehen:
logger.logrb( Level.SEVERE, „Application“, „main“, bundle, „resource.MissingInput“ );
Erfragt wird also vom ResourceBundle bundle die Kennung mit der ID resource.MissingInput.
Logger-Methoden logp(…)
Die normalen log(…)- und Hilfsmethoden loggen eine Nachricht nach einem gewissen Log-Level. Es gibt weiterhin mehrere überladene logp(…)-Methoden, die zusätzlich über einen String einen Klassennamen und Methodennamen annehmen, der dann mit geloggt wird. Die einfachste Variante ist logp(Level level, String sourceClass, String sourceMethod, String msg).
Während die normalen Logger-Methoden wie fine(…) oder severe(…) nicht auf logp(…) zurückgreifen, sondern auf log(Level level, …), gibt es zwei Methoden in Logger, die über logp(…) arbeiten, das sind entering(…) und exiting(…), die verwendet werden, um das Betreten bzw. Verlassen von Methoden zu dokumentieren.
Strings zusammenhängen mit StringJoiner
Um String zu einem großen Ergebnis zusammenzuhängen gibt es mehrere Möglichkeiten: Zum einen der Plus-Operator, zum anderen StringBuilder/StringBuffer. Doch es geht noch ein bisschen einfacher, Teilstrings zu einem Gesamtstring zusammenzubauen. String bietet zum einen die praktische Hilfsmethode join(…). Dahinter steht eine kleine Klasse StringJoiner, die auch direkt genutzt werden kann:
StringJoiner sj = new StringJoiner( „, “ );
sj.add( „1“ ).add( „2“ ).add( „3“ );
System.out.println( sj.toString() ); // 1, 2, 3
Der Join-String ist vom Typ einer CharSequence und ist der Delimiter. Er wird zwischen jedes Element gesetzt, was hinzugefügt wurde. Die im Beispiel eingesetzte Methode add(CharSequence) nimmt ein CharSequence an und liefern den aktuellen StringJoiner zurück, sodass sich die add(…)-Aufrufe kaskadieren lassen. Mit der Methode merge(StringJoiner) lässt sich der Inhalt eines andere StringJoiner integrieren.
Zusammenhängen mit Infix, Prefix und Suffix
Nicht nur das Trennzeichen selbst lässt sich angeben, sondern auch ein Startzeichen und Endzeichen. Wenn etwa die Ausgabe vorne mit einem “{“ beginnen und hinten mit einem “}” enden soll, heißt es:
StringJoiner sj = new StringJoiner( „, „, „{„, „}“ );
Nichts zum Zusammenhängen gegeben
Es kommt vor, dass dem StringJoiner nichts zum Zusammenfügen gegeben wird. Dann wird er dennoch Präfix und Suffix einsetzen.
StringJoiner sj = new StringJoiner( „, „, „{„, „}“ );
System.out.println( sj.toString() ); // {}
Ist das unerwünscht, gibt setEmptyValue(CharSequence) ein Substitut an, das genau dann zum Zuge kommt, wenn kein add(…) etwas zum StringJoiner zufügte:
StringJoiner sj = new StringJoiner( „, „, „{„, „}“ ).setEmptyValue(„<nix>“);
System.out.println( sj.toString() ); // <nix>
Zusammenfassend bietet die Klasse zwei Konstruktoren und fünf Methoden:
class java.util.StringJoiner
§ StringJoiner(CharSequence delimiter)
§ StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
§ StringJoiner add(CharSequence newElement)
§ StringJoiner merge(StringJoiner other)
§ StringJoiner setEmptyValue(CharSequence emptyValue)
§ int length()
§ String toString()