Archiv der Kategorie: Allgemein

Schneller aufrufen mit MethodType und MethodHandle

Um dynamische Programmiersprachen wie JavaScript performant auf die JVM zu bringen wurde in Java 7 der neue Bytecode invokedynamic eingeführt und das Paket lang.invoke. Java8 greift zur Umsetzung der Lambda-Ausdrücke massiv auf invokedynamic zurück.

Aufgabe des Pakets ist es, dynamisch Methoden schnell aufzurufen, und zwar deutlich schneller als Reflection das kann, weil in der JVM der Methodenaufruf so optimiert wird wie ein ganz normaler Methodenaufruf auf ein Zielobjekt. Dazu müssen aber die Typen exakt vorliegen, sodass es schärfere Anforderungen gibt als bei Reflection, dort ist z. B. der Rückgabetyp beim Aufruf irrelevant.

Um einen dynamischen Methodenaufruf zu starten ist zunächst eine exakte Beschreibung der Rückgabe- und Parametertypen nötig – das übernimmt ein MethodType-Objekt. Ist das aufgebaut, wird ein MethodHandle erfragt, ein getypter, direkt ausführbarer Verweis auf die repräsentierte Methode. Die MethodHandle-Methode invoke(…) führt dann den Aufruf mit gegebenen Argumente auf.

Dazu ein Beispiel. Wir möchten auf einem Rectangle-Objekt die Methode union(Rectangle) aufrufen, um als Ergebnis ein neues Rectangle zu bekommen, was die beiden Rechtecke vereinigt.

Object rect1 = new Rectangle( 10, 10, 10, 10 );

String methodName = "union"; 

Class<?> resultType = Rectangle.class;

Object rect2 = new Rectangle( 20, 20, 100, 100 );

Class<? > parameterType = rect2.getClass();

rect1 ist das eigentliche Objekt auf dem die methodName aufgerufen werden soll. resultType ist der Ergebnistyp den wir von der Methode erwarten, als Class-Objekt. rect2 ist das Argument für union(…). Der parameterType für die union(…)-Methode ergibt sich aus dem Class-Objekt vom rect2.

MethodType mt = MethodType.methodType( resultType, parameterType );

MethodHandle methodHandle = MethodHandles.lookup().findVirtual(

                              rect1.getClass(), methodName, mt );

System.out.println( methodHandle.invoke( rect1, rect2 ) );

Als erstes erfragen wir MethodType und geben Ergebnis- und Parametertyp an; noch nicht den Methodennamen, hier geht es nur um die Typen. Der MethodHandle verheiratet den Methodennamen und die Typangaben mit einer Klasse, die so eine Methode anbietet. invoke(…) führt letztendlich den Aufruf aus; das erste Argument ist das „this“-Objekt, also das Objekt auf dem die Methode aufgerufen werden soll, als nächstes folgenden die Argumente von union(…), also das zweite Rechteck. Als Ergebnis bekommen wir das Rectangle-Objekt, was genau der Vereinigung entspricht.

Java DB nicht mehr in Java 9

Gerade habe ich angefangen mein JDBC-Kapitel zu aktualisieren, weg von HSQLDB hin zur mitgelieferten  Java DB. Ich habe die Java DB-Datenbank gestartet, den Text und Beispiele umgeschrieben, usw. Irgendwie habe ich das db-Verzeichnis von Java 8 genutzt, ohne das mir das aufgefallen wäre. Das Kapitel ist fertig gewesen, da wollte ich schauen, ob Java 9 die aktuelle Version von Apache DB nutzt und was ist? Arrrg. In Java 9 ist die Java DB rausgeflogen. Verdammt. Alle Änderungen wieder rückgängig machen, es bleibt vorerst bei HSQLDB.

Insel Java 9 Update; Statusbericht 1

Wie man an den Beiträgen hier im Blog erkennen kann, sind viele Absätze aktualisiert worden mit den Methoden aus Java 9. Der Kleinkram ist komplett beschrieben, was jetzt noch fehlt:

  • Modulsystem natürlich, größter neu zu schreibender Text
  • Reactive Programming mit  java.util.concurrent.Flow.*, allerdings überlege ich noch, wie tief ich das beschreiben möchte

Im Allgemeinen war das Update auf Java 9 in Ordnung, newInstance() auf Class habe ich öfters gebraucht, das ist jetzt deprecated. Mein Bsp. mit der Javadoc API funktioniert nicht mehr, das Modulsystem macht mir einen Strich durch das Programm.

Mit der Gewichtung Swing/Java FX bin ich nicht zufrieden. Java FX ist weiterhin kein Teil der offiziellen Java SE, sondern nur Teil vom Oracle/Open JDK. Außerdem ist die Zukunft auch hier unsicher, jetzt, wo schon so viele Entwickler vom Projekt. abgezogen wurden. Der Trend geht ganz klar Richtung Web, sodass ich eigentlich beide Kapitel massiv kürzen sollte bis nur noch eine Einleitung stehen bleibt und noch mal ein Web-Kapitel ergänzen sollte. Was ist eure Meinung?

StackWalker and Stack-Walking API

Der Stack-Trace, den Java über die StackTraceElement bietet, ist relativ arm an Informationen, und die Standardausgabe über die Throwable-Methode printStackTrace(…) ist unübersichtlich. Aus Performance-Gründen können sogar Einträge fehlen, so dokumentiert es die Javadoc an der Methode:

Some virtual machines may, under some circumstances, omit one or more stack frames from the stack trace. In the extreme case, a virtual machine that has no stack trace information concerning this thread is permitted to return a zero-length array from this method.

Zudem fehlen spannende Informationen wie Zeitpunkte, und ein Thread.getAllStackTraces() ist bei vielen Threads und tiefen Aufrufhierarchien sehr langsam. Summa summarum: Ein guter Debugger oder ein Analysetool mit Zugriff auf die JVM-Eigenschaften ist für die ernsthafte Entwicklung unumgänglich.

In Java 9 hat Oracle die “JEP 259: Stack-Walking API” umgesetzt. Ein java.lang.StackWalker wandert die Aufrufhierarchie ab und repräsentiert die Aufrufhierarchie als StackFrame-Objekte von dort, wo der Stack generiert wurde, nach unten zur Aufrufstelle. Es gibt mehrere überladenen statische getInstance(…)-Methoden, die einen StackWalker generieren. Wir können dann mit

  • void forEach(Consumer<? super StackFrame> action) oder
  • <T> T walk(Function<? super Stream<StackFrame>, ? extends T> function)

über die StackFrames laufen.

Beispiel:

public static void showTrace() {

  List<StackFrame> frames =

    StackWalker.getInstance( Option.RETAIN_CLASS_REFERENCE )

               .walk( stream  -> stream.collect( Collectors.toList() ) );

  for ( StackFrame stackFrame : frames )

    System.out.println( stackFrame );

}

Da alle Informationen zu liefern die Geschwindigkeit senkt, und vielleicht unnötig viel Arbeit verursacht, deklariert die Aufzählung StackWalker.Option die Konstanten RETAIN_CLASS_REFERENCE, SHOW_HIDDEN_FRAMES, SHOW_REFLECT_FRAMES für unterschiedliche Vollständigkeit der Informationen.  Die Aufzählungen sind ein Argument für getInstance(…). Um die Class-Objekte über getDeclaringClass() abrufen zu können, muss getInstance(…) mit der Option.RETAIN_CLASS_REFERENCE gesetzt sein.

Beispiel: Die forEach(…)-Methode eines Streams konsumiert einen Consumer auf jedem Element. Finde heraus, ob unsere Consumer-Methode accept(…) indirekt von einer Klasse aus dem Paket  java.util.concurrent aufgerufen wurde:

Consumer<String> walkStack = String -> {

  StackWalker.getInstance( Option.RETAIN_CLASS_REFERENCE )

        .walk( stream -> stream.map( StackFrame::getDeclaringClass )

                               .map( Class::getPackageName )

                               .filter( s -> s.startsWith( "java.util.concurrent" ) )

                               .findFirst() )

                               .ifPresentOrElse( e -> System.out.println( "Durch j.u.c gelaufen" ),

                                                 () -> System.out.println( "Nicht durch j.u.c gelaufen" ) );

};

Stream.of( "Hallo" ).forEach( walkStack );            // Nicht durch j.u.c gelaufen

Stream.of( "Hallo" ).parallel().forEach( walkStack ); // Durch j.u.c gelaufen

 

ProcessHandle und Prozess-IDs

Ein ProcessHandle ist ein neuer Typ in Java 9, der native Prozesse identifiziert. Wir bekommen Exemplare entweder über

  • die Process-Objektmethode toHandle(),
  • die statische Methodecurrent(),
  • allProcesses(), die alle Prozesse über einen Stream<ProcessHandle> liefert,
  • die statische ProcessHandle-Methode of(long pid), die uns das ProcessHandle in ein Optional verpackt,
  • mit einem vorhandenen ProcessHandle können wir weiterhin mit children() und descendants() einen Stream<ProcessHandle> erfragen und mit parent() auf die Eltern zugreifen.

Jeder Prozess verfügt über eine identifizierende long-Ganzahl, die getPid() auf dem ProcessHandle liefert. Weitere Details zu den Startparametern offenbare ProcessHandle.Info-Ojekte.

Beispiel: Gib alle vorhanden Informationen über alle Prozesse aus:

Consumer<ProcessHandle> log = handle ->

  System.out.printf( "PID=%s, Root?=%s, Info=%s%n",

                     handle.getPid(), !handle.parent().isPresent(), handle.info() );

ProcessHandle.allProcesses().forEach( log );

Die Ausgabe kann so aussehen:

PID=0, Root?=true, Info=[]

PID=4, Root?=true, Info=[]

...

PID=4368, Root?=true, Info=[]

PID=4568, Root?=true, Info=[user: Optional[Yoga\Christian], cmd: C:\Windows\System32\sihost.exe, startTime: Optional[2017-02-04T20:54:07.601Z], totalTime: Optional[PT3M39.703125S]]

PID=4592, Root?=true, Info=[user: Optional[Yoga\Christian], cmd: C:\Windows\System32\svchost.exe, startTime: Optional[2017-02-04T20:54:07.621Z], totalTime: Optional[PT14.9375S]]

PID=4628, Root?=true, Info=[]

...

ProcessHandle implementiert vernünftig equals(…) und auch Comparable<ProcessHandle>; die Sortierung ist nach der Prozess-ID.

Methoden-Delegation

Einige Methoden aus Process delegieren an den assoziierten ProcessHandle. Die Methoden heißen gleich.

Process-Methoden Implementierung
long getPid() return toHandle().getPid();
ProcessHandle.Info info() return toHandle().info();
Stream<ProcessHandle> children() return toHandle().children();
Stream<ProcessHandle> descendants() return toHandle().descendants();

Weiterhin gibt es onExit(), supportsNormalTermination(), isAlive(), destroy() und destroyForcibly() auf beiden Typen Process und ProcessHandle.

 

Über Objekte vom Typ ProcessHandle.Info lassen sich weitere Details zum Prozess erfragen; die Rückgaben sind Optional, weil die Informationen vielleicht nicht vorliegen.

  • static interface java.lang.Info
  • Optional<String[]> arguments()
    Programmargumente beim Start.
  • Optional<String> command()
    Ausführbarer Pfadname vom Prozess.
  • Optional<String> commandLine()
    Konkatenation von command() und arguments() beste Repräsentation des Programmaufrufs.
  • Optional<Instant> startInstant()
    Startzeit des Prozesses.
  • Optional<Duration> totalCpuDuration()
    Bisher verbrauchte CPU-Zeit.
  • Optional<String> user()
    Benutzer dieses Prozesses.

 

Prozess-Status erfragen und das Ende einleiten

Mit Methoden von Process lässt sich der Status des externen Programms erfragen und verändern. Die Methode waitFor(…) lässt den eigenen Thread so lange warten, bis das externe Programm zu Ende ist, oder löst eine InterruptedException aus, wenn das gestartete Programm unterbrochen wurde. Der Rückgabewert von waitFor() ist der Rückgabecode des externen Programms, eine zweite Variante von waitFor(…) wartet eine gegebene Zeit. Wurde das Programm schon beendet, liefert auch exitValue() den Rückgabewert. Soll das externe Programm (vorzeitig) beendet werden, lässt sich die Methode destroyXXX() verwenden; das eigene Java-Programm kann nicht beendet werden.

abstract class java.lang.Process

  • abstractintexitValue()
    Wenn das externe Programm beendet wurde, liefert exitValue() die Rückgabe des gestarteten Programms. Ist die Rückgabe 0, deutet das auf ein normales Ende hin. Läuft das Programm noch, gibt es eine IllegalThreadStateException.
  • booleanisAlive()
    Lebt der von der JVM gestartete Unterprozess noch? Ruft intern exitValue() auf und prüft auf IllegalThreadStateException.
  • abstractvoiddestroy()
    Beendet das externe Programm.
  • ProcessdestroyForcibly()
    Standardmäßig wie destroy(), sollte aber von Unterklassen anders implementiert werden. Das brutale Beenden dauert etwas, sodass isAlive() noch eine kurze Zeit true zurückgeben kann.
  • boolean supportsNormalTermination()
    Liefert true, wenn das Programm mit destroy() ohne Probleme beendet werden kann. Unterklassen müssen die Methode überschreiben, sie löst standardmäßig eine UnsupportedOperationException Neue Methode in Java 9.
  • abstractvoidwaitFor()throwsInterruptedException
    Wartet auf das Ende des externen Programms (ist es schon beendet, muss nicht gewartet werden), sonst blockiert die Methode, und liefert dann abschließend den exitValue().
  • booleanwaitFor(longtimeout,TimeUnitunit)throwsInterruptedException
    Wartet die angegebene Zeit auf das Ende des gestarteten Programms. Wurde das externe Programm schon beendet, kehrt die Methode sofort zurück und liefert true; den Exit-Code liefert exitValue() weil hier, anders als bei waitFor() der Rückgabecode ein boolean Läuft das Programm noch, und ist es nicht nach timeout Zeiteinheiten beendet, kehrt die Methode mit false zurück. Die Rückgabe ist true, wenn das gestartete Programm in dem Zeitfenster beendet wurde. Hinweis: Die Methode bricht das externe Programm nicht ab, wenn es nach Überschreiten der Zeit noch läuft. Diese Logik muss ein Programmierer übernehmen und if ( ! waitFor(…) ) mit destroy() kombinieren.
  • CompletableFuture<Process> onExit()
    Kehrt nicht-blockierend direkt zurück und erlaubt später über das CompletableFuture Zugriff auf den Process. CompletableFuture ermöglicht die einfache Verkettung der Art onExit().thenApply( … ) oder process.onExit().whenComplete( (p, ex) -> System.out.printf(„Prozess %d beendet%n“, p.getPid())). Neu in Java 9.

Neues findAll(…) in Scanner

Immer dann, wenn ein Scanner mit einem regulären Ausdruck konfiguriert wurde, wird intern der Zustand vom dafür zugewiesenen Matcher aktualisiert. Die Scanner-Methode match() liefert einen MatchResult der letzten Operation, allerdings folgt eine IllegalStateException, wenn es keinen Match gab oder der letzte Match nicht erfolgreich war.

Beispiel: Finde alles, was im inneren zwischen <b></b> steht:

Scanner sc = new Scanner( "Wichtig: <b>essen</b> und <b>trinken</b>!";
 while ( sc.findInLine( "<b>(.+?)</b>" ) != null )
   System.out.println( scanner.match().group( 1 ) );

Die Ausgabe ist dann „essen“ und „trinken“.

Java 9 bringt die neue Methode Stream<MatchResult> findAll(Pattern pattern)/ findAll(String patString) mit.

Beispiel: Finde alles, was im inneren zwischen <b></b> steht:

new Scanner( "<b>essen</b> und\n <b>trinken</b>" )

  .findAll( "<b>(.+?)</b>" )

  .forEach( matchresult -> System.out.println( matchresult.group( 1 ) ) );

Die Ausgabe ist ebenfalls „essen“ und „trinken“.

Stream vom Scanner-Tokens generieren

Die in Java 9 eingeführte Objektmethode stream() ist eine sehr gute Ergänzung, denn sie liefert einen Stream<String> von zerlegten Strings.

Beispiel: Durch Komma getrennte String sollen durch ein Zeilenumbruch wieder zusammengefügt werden:

String s = "CNN, Politico, LA Times, New York Times";

System.out.println( new Scanner(s).useDelimiter( "\\s*,\\s*" ).tokens()

                                  .collect( Collectors.joining("\n") ) );

Index-bezogene Programmargumente auf Korrektheit prüfen

Im Kapitel über Ausnahmen haben wir schon auf die Notwendigkeit hingewiesen, Wertebereiche zu prüfen und im Fehlerfall Ausnahmen  wie IllegalArgumentException oder IndexOutOfBoundsException auszulösen um keine falschen Werte in das Objekt zu lassen.

In Java 9 sind drei Methoden hinzugekommen, die die gültigen Werbereiche von Index-basierten Methoden prüfen können und im Fehlerfall eine IndexOutOfBoundsException auslösen.

class java.util.Objects

  • static int checkIndex(int index, int length)
  • static int checkFromToIndex(int fromIndex, int toIndex, int length)
  • static int checkFromIndexSize(int fromIndex, int size, int length)

Beispiel: Implementierung der get(int)-Methode in ArrayList:

public E get(int index) {

  Objects.checkIndex(index, size);

  return elementData(index);

}

 

Geld und Währung in Java

Geldbeträge repräsentieren

Für Geldbeträge gibt es in Java keinen eigenen Datentyp und so kann eine Speicherung je nach Programm immer anders aussehen. Es bieten sich an:

  • BigDecimal: Vorteil sind die präzisen Berechungen und die wählbaren Rundungen
  • Paar von int long: Getrenntes Speichern der Vor-/Nachkommastellen

Hinweis: Die primitiven Datentypen double und float sind wegen ihrer Unfähigkeit Vielfaches von 0.01 korrekt dazustellen nicht empfohlen; Rundungsfehler treten schnell auf.

Money and Currency API

Im „JSR 354: Money and Currency API“ wird ein eigener Datentyp für Geldbeträge definiert, und die Typen sollen eigentlich in Java 9 aufgenommen werden, doch dazu kam es nicht. Dennoch sind die Typen interessant und die Referenzimplementierung Moneta ein Blick wert: http://javamoney.github.io/ri.html. Neben Geldbeträgen erlaubt die kleine Bibliothek auch Umrechungen, Formatierungen und eigene Währungen.

ISO 4217

Währungen werden durch Währungscodes beschrieben, und die Definition findet sich in der Norm ISO 4217. Einige ISO-Codes sind:

ISO 4217-Code Währung/Einheit Land
EUR Euro Länder der europäischen Währungsunion
CNY Renminbi China
DKK Krone Dänemark
GBP Pfund Vereinigtes Königreich
INR Rupie Indien
USD Dollar USA, Ecuador, …
XAU Feinunze Gold  

Einige ISO 4217-Codes

Die Tabelle lässt am letzten Eintrag erkennen, dass es auch ISO-Codes für Edelmetalle und sogar Fonds gibt. Für jedes Kürzel gibt es ebenfalls einen numerischen Code.

Währungen in Java repräsentieren

Java repräsentiert Währungen durch die Klasse java.util.Currency. Exemplare der Klasse werden durch eine Fabrikmethode getInstance(String currencyCode) erfragt, bzw. aus einer Aufzählung ausgewählt.

Beispiel: Gib alle im System angemeldeten Währungen mit ein paar Informationen aus:

Currency.getAvailableCurrencies().stream()

        .sorted( Comparator.comparing( Currency::getCurrencyCode ) )

        .forEach( c -> System.out.printf( "%s, %s, %s, %s%n",

                                          c.getCurrencyCode(), c.getSymbol(),

                                          c.getDisplayName(), c.getNumericCode() ) );

Die Ausgabe beginnt so:

ADP, ADP, Andorranische Pesete, 20

AED, AED, VAE-Dirham, 784

AFA, AFA, Afghanische Afghani (1927–2002), 4

AFN, AFN, Afghanischer Afghani, 971

ALL, ALL, Albanischer Lek, 8

Unterschiede suchen mit Arrays.mismatch (…)

Neu in Java 9 sind weiterhin diverse Methoden

  • int mismatch(XXX[] a, XXX[] b)
  • int mismatch(XXX[] a, int aFromIndex, int aToIndex, XXX[] b, int bFromIndex, int bToIndex)

Sie geben den Index auf das erste Element zurück was ungleich ist. Sind beide Felder gleich ist die Rückgabe -1.

Für Objekt-Array gibt es weiterhin:

  • int mismatch(Object[] a, Object[] b)
  • int mismatch(Object[] a, int aFromIndex, int aToIndex, Object[] b, int bFromIndex, int bToIndex)
  • <T> int mismatch(T[] a, T[] b, Comparator<? super T> cmp)
  • <T> int mismatch(T[] a, int aFromIndex, int aToIndex, T[] b, int bFromIndex, int bToIndex, Comparator<? super T> cmp)

Die erste/zweite Methode nutzt direkt equals(…), die dritte/vierte einen externen Comparator.

Lexikografische Array-Verbleiche mit compare (…) und compareUnsigned(…)

Diverse in Java 9 eingeführte int compareXXX(XXX[] a, XXX[] b)-Methoden gehen die Arrays ab und testen alle Paare auf ihre Ordnung. Es gibt die von Comparator bekannte Rückgabe: ist jedes a[i] == b[i] ist die Rückgabe 0. Ist in der Abfragefolge ein a[i] kleiner als b[i] ist, dann ist die Rückgabe negativ, ist ein a[i] größer als b[i] ist die Rückgabe positiv. Die Methode ist überladen mit einer Variante, die einen Bereich im Array auswählt: compare(XXX[] a, int aFromIndex, int aToIndex, XXX[] b, int bFromIndex, int bToIndex). Für byte, short, int und long gibt es weiterhin eine Vergleichsmethode ohne Vorzeichen über den gesamten Wertebereich:

  • int compareUnsigned(XXX[] a, XXX[] b)
  • int compareUnsigned(XXX[] a, int aFromIndex, int aToIndex, XXX[] b, int bFromIndex, int bToIndex)

Für Objekte gibt es eigene Methoden:

  • static <T extends Comparable<? super T>> int compare(T[] a, T[] b)
    Vergleiche zwei Objekt-Arrays, wobei der Comparator die Gleichheit der Objektpaare feststellt.
  • static <T extends Comparable<? super T>> int compare(T[] a, int aFromIndex, int aToIndex, T[] b, int bFromIndex, int bToIndex)
    Vergleiche von Ausschnitten.
  • static <T> int compare(T[] a, T[] b, Comparator<? super T> cmp)
  • static <T> int compare(T[] a, int aFromIndex, int aToIndex,T[] b, int bFromIndex, int bToIndex, Comparator<? super T> cmp)
    Vergleicht mit Hilfe eines externen Comparator-Objekts.

Objekt-Arrays mit Arrays.equals(…) und Arrays.deepEquals(…) vergleichen

Die Arrays.equals(…)-Methode kann auch beliebige Objektfelder vergleichen, doch nutzt sie dann nicht die Identitätsprüfung per ==, sondern die Gleichheit per equals(…). Eine seit Java 9 hinzugekommene Methode fragt einen Comparator.

Beispiel

Enthalten zwei String-Arrays die gleichen Wörter, wobei Groß-/Kleinschreibung keine Rolle spielt?

String[] words1 = { "Zufriedenheit", "übertrifft" , "Reichtum" };

String[] words2 = { "REICHTUM", "übertrifft" , "ZuFRIEDEnheit" };

Arrays.sort( words1, String.CASE_INSENSITIVE_ORDER );

Arrays.sort( words2, String.CASE_INSENSITIVE_ORDER );

System.out.println( Arrays.equals( words1, words2, String.CASE_INSENSITIVE_ORDER ) );

class java.util.Arrays

  • staticbooleanequals(Object[]a,Object[]a2)
    Vergleicht zwei Arrays mit Objektverweisen. Ein Objekt-Array darf null enthalten; dann gilt für die Gleichheit e1==null ? e2==null : equals(e2).
  • staticbooleandeepEquals(Object[]a1,Object[]a2)
    Liefert true, wenn die beiden Arrays ebenso wie alle Unter-Arrays – rekursiv im Fall von Unter-Objekt-Arrays – gleich sind.
  • static <T> boolean equals(T[] a, T[] a2, Comparator<? super T> cmp)
    Vergleicht zwei Arrays und compare(a[i], b2[i]) muss für alle Pärchen 0 sein, damit beide Elemente als gleich gelten. Neu in Java 9.
  • static <T> boolean equals(T[] a, int aFromIndex, int aToIndex, T[] b, int bFromIndex, int bToIndex, Comparator<? super T> cmp)
    Vergleiche Ausschnitte von Arrays mit einem Comparator. Neu in Java 9.

Arrays mit Arrays.equals(…) und Arrays.deepEquals(…) vergleichen

Die statischen Methoden Arrays.equals(…) vergleichen, ob zwei Arrays die gleichen Inhalte besitzen; dazu ist die überladene Methode für alle wichtigen Typen definiert. Wenn zwei Arrays tatsächlich die gleichen Inhalte besitzen, ist die Rückgabe der Methode true, sonst false. Natürlich müssen beide Arrays schon die gleiche Anzahl von Elementen besitzen, sonst ist der Test sofort vorbei und das Ergebnis false. Im Fall von Objekt-Arrays nutzt Arrays.equals(…) nicht die Identitätsprüfung per ==, sondern die Gleichheit per equals(…).

Beispiel

Vergleiche drei Arrays:

int[] array1 = { 1, 2, 3, 4 };

int[] array2 = { 1, 2, 3, 4 };

int[] array3 = { 9, 9, 2, 3, 9 };

System.out.println( Arrays.equals( array1, array2 ) );              // true

System.out.println( Arrays.equals( array2, 1, 3, array3, 2, 4 ) );  // true

Ein Vergleich von Teil-Arrays ist erst in Java 9 hinzugekommen.

Bei unterreferenzierten Arrays betrachtet Arrays.equals(…) das innere Array als einen Objektverweis und vergleicht es auch mit equals(…) – was jedoch bedeutet, dass nicht identische, aber mit gleichen Elementen referenzierte innere Arrays als ungleich betrachtet werden. Die statische Methode deepEquals(…) bezieht auch unterreferenzierte Arrays in den Vergleich ein.

Beispiel

Unterschied zwischen equals(…) und deepEquals(…):

int[][] a1 = { { 0, 1 }, { 1, 0 } };
 int[][] a2 = { { 0, 1 }, { 1, 0 } };
 System.out.println( Arrays.equals( a1, a2 ) );     // false
 System.out.println( Arrays.deepEquals( a1, a2 ) ); // true
 System.out.println( a1[0] );                       // zum Beispiel [I@10b62c9
 System.out.println( a2[0] );                       // zum Beispiel [I@82ba41

Dass die Methoden unterschiedlich arbeiten, zeigen die beiden letzten Konsolenausgaben: Die von a1 und a2 unterreferenzierten Arrays enthalten die gleichen Elemente, sind aber zwei unterschiedliche Objekte, also nicht identisch.

Hinweis

deepEquals(…) vergleicht auch eindimensionale Arrays:

Object[] b1 = { "1", "2", "3" };
 Object[] b2 = { "1", "2", "3" };
 System.out.println( Arrays.deepEquals( b1, b2 ) ); // true

class java.util.Arrays

  • staticbooleanequals(XXX[]a,XXX[]a2)
    Vergleicht zwei Arrays gleichen Typs und liefert true, wenn die Arrays gleich groß und Elemente paarweise gleich sind. XXX steht stellvertretend für boolean, byte, char, int, short, long, double, float.
  • staticbooleanequals(XXX[] a, int aFromIndex, int aToIndex, XXX[] b, int bFromIndex, int bToIndex)
    Vergleicht zwei Arrays, bleibt jedoch in den gewählten Ausschnitten. Neue Methoden in Java 9.
  • staticbooleanequals(Object[]a,Object[]a2)
    Vergleicht zwei Arrays mit Objektverweisen. Ein Objekt-Array darf null enthalten; dann gilt für die Gleichheit e1==null ? e2==null : equals(e2).
  • staticbooleandeepEquals(Object[]a1,Object[]a2)
    Liefert true, wenn die beiden Arrays ebenso wie alle Unter-Arrays – rekursiv im Fall von Unter-Objekt-Arrays – gleich sind.

Stream iterate(…)-Methoden

Die zwei statischen iterate(…)-Methoden generieren einen Stream aus einem Startwert und einer Funktion, die das nächste Element produziert.  Bei iterate(T seed,
UnaryOperator<T> f) ist der Strom unendlich, bei der zweiten – in Java 9 hinzugekommenen Methode – iterate(…, Predicate<? super T> hasNext, …)-Methode beendet ein erfülltes Prädikat den Strom und erinnert an eine klassische for-Schleife. Der Abbruch über ein Prädikat ist sehr flexibel, denn bei der ersten iterate(…)-Methode ist das Stoppen immer ein Problem und so folgt oftmals ein limit(…) oder takeWhile(…) zum Limitieren der Elemente.

Beispiele

Produziere Permutationen eines Strings.

UnaryOperator<String> shuffleOp = s -> {

  char[] chars = s.toCharArray();

  for ( int index = chars.length - 1; index > 0; index-- ) {

    int rndIndex = ThreadLocalRandom.current().nextInt( index + 1 );

    if ( index == rndIndex ) continue;

    char c = chars[ rndIndex ];

    chars[ rndIndex ] = chars[ index ];

    chars[ index ] = c;

  }

  return new String( chars );

};

String text = "Sie müssen nur den Nippel durch die Lasche ziehn";

Stream.iterate( text, shuffleOp ).limit( 10 ).forEach( System.out::println );

Erzeuge einen BigInteger-Stream ab 10 Millionen alle Zahlen aus, bis mit hoher Wahrscheinlichkeit eine Zufallszahlen erscheint.

Predicate<BigInteger> isNotPrime = i -> ! i.isProbablePrime( 10 );

UnaryOperator<BigInteger> incBigInt = i -> i.add( BigInteger.ONE );

Stream.iterate( BigInteger.valueOf( 10_000_000 ), isNotPrime, incBigInt )

      .forEach( System.out::println );

Präfix-Operation der Stream-API

Unter einem Präfix verstehen wir eine Teilfolge eines Streams, die beim ersten Element beginnt. Wir können mit limit(long) selbst einen Präfix generieren, doch im Allgemeinen geht es darum eine Bedingung zu haben, die alle Elemente eines Präfix-Streams erfüllen müssen, und wenn es für ein Element nicht gilt, dann den Stream zu beenden. Java 9 deklariert für dafür zwei neue Methoden takeWhile(…) und dropWhile(…):

  • default Stream<T> takeWhile(Predicate<? super T> predicate)
  • default Stream<T> dropWhile(Predicate<? super T> predicate)

Die deutsche Übersetzung von takeWhile(…) wäre „nimm solange predicate gilt“ und dropWhile(…) „lass fallen, solange predicate gilt“.

Beispiel: Der Stream soll bei Eintreffen des Wortes „Trump“ sofort enden:

new Scanner( "Dann twitterte Trump am 7. Nov. 2012: "

           + "'It's freezing and snowing in New York--we need global warming!'" )

  .useDelimiter( "\\P{Alpha}+" ).tokens()

  .takeWhile( s -> !s.equalsIgnoreCase( "trump" ) )

  .forEach( System.out::println );  // Dann twitterte

}

Der Stream soll nach dem längsten Präfix beginnen, nämlich dann, wenn eine Zahl negativ wurde:

Stream.of( 1, 2, -1, 3, 4, -1, 5, 6 )

      .dropWhile( i -> i > 0 )

      .forEach( System.out::println );    // -1 3 4 -1 5 6

Das Element, das das Prädikat erfüllt, ist selbst das erste Element im neuen Stream. Wir können es mit skip(1) überspringen.

Erfüllt schon bei takeWhile(…) das erste Element nicht das Prädikat ist die der Ergebnis-Stream leer, erfüllt kein Element die Bedingung ist das Ergebnis wie der Ursprungs-Stream. takeWhile(…) und dropWhile(…) können zusammen verwendet werden: so liefert Stream.of( 1, 2, -1, 3, 4, -1, 5, 6 ).dropWhile( i -> i > 0 ).skip( 1 ).takeWhile( i -> i > 0 ).forEach( System.out::println ); die Ausgaben 3 4.

Hinweis: Präfixe sind nur für geordnete Streams sinnvoll. Und wenn Streams parallel sind müssen sie für die Präfixberechnung wieder in Reihe gebracht werden, das ist eine eher teurer Operation.

Funktion zur String-Ersetzung einsetzen

Nach einem Teilstring über einen regulären Ausdruck zu suchen, und diesen dann nach einer Transformation wieder zurückzuschreiben ist eine häufige Operation, für die es in Java 9 zwei neue Methoden gibt.

  • String replaceFirst(Function<MatchResult, String> replacer)
  • String replaceAll(Function<MatchResult, String> replacer)

Beispiel

String s = Pattern.compile( "(\\d+)\\s+[€|EUR]+" )

                  .matcher( "2 Perlen, 10 € in bar und 10000 EUR auf dem Konto." )

                  .replaceAll( matchresult ->

                    matchresult.group( 1 ).length() < 4 ? "wenig Kohle" : "viel Kohle" );

System.out.println( s ); // 2 Perlen, wenig Kohle in bar und viel Kohle auf dem Konto.

Datumsklasse java.time.LocalDate

Ein Datum (ohne Zeitzone) repräsentiert die Klasse LocalDate. Damit lässt sich zum Beispiel ein Geburtsdatum repräsentieren.

Ein temporales Objekt kann über die statischen of(…)-Fabrikmethoden aufgebaut, über ofInstant(Instant instant, ZoneId zone) oder von einem anderen temporalen Objekt abgeleitet werden. Interessant sind die Methoden, die mit einem TemporalAdjuster arbeiten.

Beispiel

 LocalDate today = LocalDate.now();
 LocalDate nextMonday = today.with( TemporalAdjusters.next( DayOfWeek.SATURDAY ) );
 System.out.printf( "Heute ist der %s, und frei ist am Samstag, den %s",
                    today, nextMonday );

Mit den Objekten in der Hand können wir diverse Getter nutzen und einzelne Felder erfragen, etwa getDayOfMonth(), getDayOfYear() (liefern int) oder getDayOfWeek(), das eine Aufzählung vom Typ DayOfWeek liefert, und getMonth(), das eine Aufzählung vom Typ Month Weiterhin gibt es long toEpochDay() und und in Java 9 long toEpochSecond(LocalTime time, ZoneOffset offset).

Dazu kommen Methoden, die mit minusXXX(…) oder plusXXX(…) neue LocalDate-Objekte liefern, wenn zum Beispiel mit minusYear(long yearsToSubtract) eine Anzahl Jahre zurückgelaufen werden soll. Durch die Negation des Vorzeichens kann auch die jeweils entgegengesetzte Methode genutzt werden, sprich LocalDate.now().minusMonths(1) kommt zum gleichen Ergebnis wie LocalDate.now().plusMonths(-1). Die withXXX(…)-Methoden belegen ein Feld neu und liefern ein modifiziertes neues LocalDate-Objekt.

Von einem LocaleDate lassen sich andere temporale Objekte bilden, atTime(…) etwa liefert LocalDateTime-Objekte, bei denen gewisse Zeit-Felder belegt sind. atTime(int hour, int minute) ist so ein Beispiel. Mit until(…) lässt sich eine Zeitdauer vom Typ Period liefern. Interessant sind zwei neue Java 9-Methoden, die einen Strom von LocalDate-Objekten bis zu einem Endpunkt liefern.

  • Stream<LocalDate> datesUntil(LocalDate endExclusive)
  • Stream<LocalDate> datesUntil(LocalDate endExclusive, Period step)

java.time.Duration

Eine Klasse Duration repräsentiert Dauern von Zeiten und ist weder mit Zeitzonen verbunden noch mit anderen Zeitleisten. Daher ist auch ein Tag idealisiert exakt 24 Stunden lang, Schaltsekunden kennt die Klasse nicht, und einen Tag zu addieren heißt, 24 Stunden aufzurechnen.

Die interne Berechnungseinheit ist Sekunden bzw. Nanosekunden, auch wenn Hilfsmethoden Zeiteinheiten bis Stunden erlauben. Darüber wird eine Duration auch aufgebaut, über ofXXX()-Methoden wie ofSeconds(long seconds, long nanoAdjustment) oder ofDays(long days). Differenzen bildet wieder Duration between(Temporal startInclusive, Temporal endExclusive), wobei Temporal eine Schnittstelle ist, die etwa von LocalDate, LocalDateTime, LocalTime, OffsetDateTime, OffsetTime, ZonedDateTime, Year, YearMonth, Instant implementiert wird, nicht aber von Period.

Beispiel: Wie viel Zeit vergeht zwischen der Ausführung?

Instant start = Instant.now();
 try {
   Files.walk( Paths.get( System.getProperty( "user.home" ) ) ).count();
 } catch ( Exception e ) { }
 Instant end = Instant.now();
 System.out.println( Duration.between( start, end ).toMillis() + " ms" );

Abgewandelt wird eine Duration wieder über withXXX(…) oder die minusXXX(…)/plusXXX(…)-Methoden. Duration dividedBy(long divisor) teilt die Duration durch eine Zeit. Die neue Java 9-Methode long dividedBy(Duration divisor) sagt, wie oft der divisor in der Duration liegt.

toNanos(), toMillis(), toSeconds() – neu in Java 9 –, toMinutes(), toHours(), toDays() konvertieren in ein long, getNano() und getSeconds() liefern die zwei Bestandteile einer Duration als long. Wichtig ist der Unterschied: Die Getter liefern den Feldwert von Duration, während toXXX() immer konvertiert.

Beispiel

Duration aSecond = Duration.of( 1, ChronoUnit.MINUTES );
out.println( aSecond.getSeconds() ); // 60
out.println( aSecond.getNano() ); // 0
out.println( aSecond.toMinutes() ); // 1
out.println( aSecond.toSeconds() ); // 60
out.println( aSecond.toNanos() ); // 60000000000

In Java 9 kommen Methoden hinzu, die die Anteile an Minuten, Sekunden, usw. erfragen, und zwar long toDaysPart(), int toHoursPart(), int toMinutesPart(), int toSecondsPart(), int toMillisPart() und int toNanosPart().