Archiv der Kategorie: Java 8

Java 8 Syntax-Migration mit Eclipse

Seit der Version Eclipse 4.4 (bzw. Eclipse 4.3 mit Java 8-Erweiterung) integriert die IDE einen Code-Transformator, der Klassen-Implementierung von funktionalen Schnittellen in Lambda-Ausdrücke verkürzen kann. Diese Transformationen ist über die Quick-Fix/Quick-Assist möglich.

Eclipse_ConvertToLambda

Leider kann diese Umsetzung (bisher) nicht für ein ganzes Projekt erfolgen, und keine umfangreiche Code-Basis komplett und automatisch auf die neuen Lambda-Ausdrücke gebracht werden.

Java 8 Syntax-Migration mit NetBeans

Die aktuelle NetBeans IDE bringt einen Code-Transformer mit, der elegant die Code-Basis durchsucht und Transformationen automatisch durchführt. Das Menü Refactor bietet den Unterpunkt Inspect and Transform…, der zu einem Dialog führt:

NB_InspectAndTransform

Unter Manage… öffnet sich ein weiter Dialog, bei dem JDK Migration Support aktiviert ist und einige Änderungen voreingestellt sind, neu für Java 8 ist Covert to Lambda or Method References. Auch die Transformationen in die neue Java 7-Syntax sind voreingestellt, aktiviert werden kann: Can use Diamond, Convert to try-with-resources, Join catch sections using mulitcatch, Use specific catch und Use switch over Strings when possible.

NB_InspectAndTransform2

Wer neu Programmcode mit NetBeans schreibt wird gleich auf die neue Syntax hingewiesen. Vervollständigungen nutzen ganz natürlich zum Beispiel den Diamond-Operator.

Metadata space in JDK 8 HotSpot JVM

Wenn es um Java geht, müssen wir immer unterscheiden, ob es um die Sprache selbst geht, um die Bibliotheken, um die JVM oder um die Implementierung von Oracle, die das JDK darstellt. Eine zentrale Neuerung in der Version 8 der HotSpot JVM ist der Umgang bei der internen Repräsentation von Klassen und Metadaten; die Daten landen nicht mehr auf dem Java-Heap, sondern im nativen Speicher, der Metaspace heißt. Vorher gehörte ein java.lang.OutOfMemoryError: PermGen space zu bei großen Anwendungen zum Alltag[1], das Problem ist (im Prinzip) Vergangenheit, es gibt kein Perm-Space mehr in der JDK 8 JVM; die Oracle JRockit und JVM von IBM hatten übrigens immer Metaspace. Der Metaspace-Speicherbedarf kann mit dem Schalter MaxMetaspaceSize begrenzt werden, ein Übertritt führt zur java.lang.OutOfMemoryError: Metadata space. Eine Begrenzung ist nicht verkehrt, das Ergebnis unter Java 7 und Java 8 aber das gleiche, ein OutOfMemoryError.


[1] http://stackoverflow.com/questions/88235/dealing-with-java-lang-outofmemoryerror-permgen-space-error

Official Eclipse Support for Java 8

So ist zu lesen unter https://dev.eclipse.org/mhonarc/lists/eclipse.org-committers/msg00948.html:

The Eclipse top-level project is very proud to announce official support for Java 8. Starting with I20140318-0830 all Luna (4.4) builds contain the Eclipse support for Java™ 8. For Kepler SR2 (4.3.2) a feature patch is available. For future builds visit our downloads page.
The Java™ 8 support contains the following:
– Eclipse compiler implements all the new Java™ 8 language enhancements.
– Significant features, like Search and Refactoring, have been updated to support Java™ 8.
– Quick Assist and Clean Up to migrate anonymous class creations to lambda expressions and back.
– New formatter options for lambdas.
Note that PDE API Tools has not yet adopted the new language constructs. This will be completed in the final Luna release.
Big thanks to everyone who worked on this effort!

Java 8, es ist geschafft

Heute hat Oracle Java 8 freigegeben, es ist vollbracht. Nach langer Verzögerung können wir uns auf Lambda-Ausdrücke, einer neuen Date-Time-API und einer chicen Stream-API freuen. Auch NetBeans 8 ist mit am Start.

Die Java Insel wird in den beiden Bänden umfassend auf Java 8 eingehen, die Bücher werden in ca. 1,5 Monaten in den Buchhandlungen ausliegen und als eBook verfügbar sein. Mit den Ergänzungen bin ich komplett durch, auch das Kapitel über die Date-Time-API war nicht so übel wie gedacht, wobei mich die Typen und Hierarchien schwindlig machen machen wir auch bei den FX-Properties. Insgesamt habe ich beim Schreiben und Ausprobieren einen tiefen Eindruck in Java 8 bekommen und meine interne tutego Schulungssoftware auf Java 8 gebracht. Das passt soweit und ist rund. Ätzend ist, dass Optional nicht serialisierbar ist und bei GWT eine Sonderbehandlung braucht. Auch die Date-Time-Typen sind noch zu neu, als dass sie bei GWT, XStream, JPA berücksichtigt werden.

Kommandozeilenprogramme jrunscript und jjs

Java bringt zwei Werkzeuge im bin-Verzeichnis zum Ausführen von Skripte mit:

· jrunscript: Führt ein Skript mit einer JSR-223-kompatiblen Skript-Einige aus. Standardmäßig ist das JavaScript, weil keine anderen Skript-Engine vorinstalliert ist. Der Schalter -I bestimmt alternative Skript-Sprachen. Zur weiteren Dokumentation der Optionen siehe http://download.java.net/jdk8/docs/technotes/tools/windows/jrunscript.html.

· jjs: Führt immer JavaScript-Programme mit Nashorn aus. Siehe auch http://download.java.net/jdk8/docs/technotes/tools/windows/jjs.html für weitere Optionen. Interessant für JavaFX-Anwendungen ist der Schalter –fx, um verkürzte JavaFX-Anwendungen in JavaScript schreiben zu können.

Während also jrunscript generisch für alle Skriptsprachen sind, ist jjs exklusiv für Nashorn und bietet Angabe eines Log-Level, die Möglichkeit den abstrakten Syntaxbaum anzuzeigen, und vieles mehr.

Hinweis: Ohne Argumente geben beide in einen interaktiven Modus:

$ jjs

jjs> print(„Hallo Nashhorn“)

Hallo Nashhorn

jjs> 12+3

15

jjs>

java.lang.reflect.Parameter in Java 8

Ein Parameter repräsentiert einen Parameter einer Methode oder eines Konstruktors. Zu den neuen Methoden zählen:

final class java.lang.reflect.Parameter

implements AnnotatedElement

§ String getName()

§ int getModifiers()

§ boolean isNamePresent()

§ boolean isImplicit()

§ boolean isSynthetic()

§ boolean isVarArgs()

§ Type getParameterizedType()

§ Executable getDeclaringExecutable()

§ Class<?> getType()

§ AnnotatedType getAnnotatedType()

Die in Java 8 eingeführte finale Klasse Parameter implementiert AnnotatedElement, da seit Java 8 auch Parametervariablen annotiert sein können; die Methoden sind in der oberen Aufzählung nicht noch einmal aufgezählt.

Um an einen Parameter zu gelangen nutzen wir getParameters() auf einem Executable, also konkret Constructor, Method.

abstract class java.lang.reflect.Executable<T>
extends AccessibleObject

implements Member, GenericDeclaration

§ public Parameter[] getParameters()

Falls etwas beim Erfragen schief geht, gibt es eine MalformedParametersException, eine ungeprüfte Ausnahme.

Beispiel:

Constructor<?>[] constructors = Point.class.getDeclaredConstructors();

for ( Constructor<?> constructor : constructors ) {

System.out.println( constructor );

for ( Parameter param : constructor.getParameters() )

System.out.printf( “ %s %s%n“, param.getType(), param.getName() );

}

Mit der Ausgabe (an der abzusehen ist, dass die Parameternamen für die JVM nicht bekannt sind):

public java.awt.Point(int,int)

int arg0

int arg1

public java.awt.Point(java.awt.Point)

class java.awt.Point arg0

public java.awt.Point()

Ausgaben mit Calendar-Methoden getDisplayName(…)

Die Calendar-Klasse liefert mit getDisplayName(int field, int style, Locale locale) einen String für einen Feldwert zurück. Der style ist eine Konstante und deklariert sind folgende Konstanten: SHORT_FORMAT (SHORT), SHORT_STANDALONE, LONG_FORMAT (LONG), LONG_STANDALONE, NARROW_FORMAT, NARROW_STANDALONE, einige neu seit Java 8. Eine weiter Methoden getDisplayNames(int field, int style, Locale locale) liefert ein Map<String,Integer> mit allen String-Repräsentationen für ein Feld:

Beispiel

Calendar cal = Calendar.getInstance();

System.out.println( cal.getDisplayName( Calendar.MONTH, Calendar.LONG_FORMAT, Locale.GERMANY ) ); // Februar

System.out.println( cal.getDisplayNames( Calendar.MONTH, Calendar.LONG_FORMAT, Locale.GERMANY ) ); // {Oktober=9, Mai=4, …

SplittableRandom in Java 8

Die Klasse SplittableRandom aus dem java.util-Paket ist neu in Java 8 und hat die Aufgabe Folgen guter Zufallszahlen zu liefern. (Auch wenn die Klasse SplittableRandom heißt, hat sie mit einem java.util.Spliterator nichts zu tun.) Während bei Random eher die einzelne Zufallszahl im Mittelpunkt steht, rückt SplittableRandom Folgen von Zufallszahlen in den Mittelpunkt, die insbesondere den Dieharder-Test[1] bestehen.

Die Methoden von SplittableRandom drehen sich daher auch um Ströme von Zufallszahlen, die als IntStream, LongStream und DoubleStream geliefert werden. Zudem gibt es auch die auf Random bekannten nextXXX()-Methoden und eine Methode split(), die ein neues SplittableRandom liefert, sodass zwei parallele Threads weiterhin unabhängig gute Zufallszahlen bekommen.


[1] http://www.phy.duke.edu/~rgb/General/dieharder.php

Wunschzeit und wo die Inseln für Java 8 stehen

Jetzt wo Java 8 auf der Zielgeraden ist, und ich mit keinen API-Änderungen mehr rechnen muss, gehen auch die beiden Insel in die Endphase. Die Neuerungen sind ja im Grunde sehr große Bausteine wie Lambda-Ausdrücke und Stream-API und dann duzend verteilte Änderungen.

Nervig für mich als Autor war die Dynamik, die die API noch in der letzten Phase entwickelte; besonders trat das bei der Stream-API auf. Doch komplett fertig sind nun die großen Kapitel über

  • Lambda-Ausdrücke (ca. 30 Word-Seiten)
  • java.util.function und funktionale Programmierung (ca. 20 Word-Seiten)
  • Stream-API (ca. 30 Word-Seiten)

Alle kleineren Sprachänderungen wie Default-Methoden und statische Schnittstellenmethoden sind ebenfalls komplett fertig und verteilt.

Fast alle kleineren API-Änderungen sind ebenfalls eingeflossen und im Grunde sind damit die beiden neuen Bände fertig. Was noch fehlt — und daran arbeite ich gerade als letztes — ist die neue Date-Time-API. Große Freude bereitet mir das nicht … Vielleicht baue ich das später in der nächsten Auflage weiter aus, wenn der andere Datums-Teil (Date, Calender, …) komprimiert wird. Ebenfalls von meiner Lust abhängig wird sein, wie weit ich JFC (Swing, Java 2D) kürze und die GUI-Themen nach JavaFX bringe.

Die Insel Band 2 greift nicht alle Bibliotheken in hundertprozentiger Tiefe auf, da es Bereiche gibt, die schon sehr speziell sind, und dafür fehlt auch der Platz. Vermutlich werden ich zu folgenden (extrem spannenden Themen) nicht mehr als einen Satz bringen:

  • Neuer Typ java.util.concurrent.locks.StampedLock
  • Neuer Typ java.util.concurrent.CountedCompleter
  • Neuer Typ java.util.concurrent.ConcurrentHashMap.KeySetView
  • Neuer Typ java.util.concurrent.CompletionStage
  • Neuer Typ java.util.concurrent.CompletionException
  • Neuer Typ java.util.concurrent.CompletableFuture
  • Neuer Typ java.util.concurrent.CompletableFuture.AsynchronousCompletionTask
  • Änderungen am ForkJoinPool

Bis auf ein paar weiteren Kleinigkeiten (Implementierung vom eigenen Spliterator, Reflection-Kram, Locale-Update, …) ist sonst die Abdeckung der neuen Themen bei 100%.

Kummer macht mir die Java 8 Unterstützung der Eclipse IDE, da es bisher keine Eclipse-Version mit integriertem Java 8 Compiler gibt, nur über ein Update. Luna soll im Juni fertig sein, das wäre eigentlich zu spät. Bei NetBeans haben wir das Problem natürlich nicht.

Haben meine Leser noch spezielle Wünsche an die nächste Auflage?

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.

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).