Montag, 11. Dezember 2017

Unendliche Streams in Java 8 und 9

[This text is available in English on DZone.]

Mit Lambdas und Streams können wir seit Java 8 endlich etwas funktionaler programmieren. Ein wichtiges Konzept in funktionalen Sprachen sind unendliche Streams, die durch geeignete Bedingungen passend "abgebrochen" werden (die Streams sind natürlich nicht wirklich unendlich, sondern sie werden "lazy" - also erst bei Bedarf - ausgewertet). So etwas gibt es auch in Java, und das möchte ich mit dem folgenden Beispiel zeigen.

Es geht um den Luhn-Algorithmus zur einfachen Berechnung einer Prüfsumme. Inspiriert dazu wurde ich durch den JVM Functional Language Battle von Falk Sippach. Zu sehen gab es in dem Vortrag verschiedene Implementierungen, von klassischem, imperativem Java bis hin zu Scala, Frege und Java mit Vavr (vormals Javaslang).

Eine kurze Zusammenfassung vom Luhn-Algorithmus:
  • Gegeben sei eine nicht negative, ganze Zahl beliebiger Länge.
  • Von rechts nach links wird jede 2. Ziffer der Zahl verdoppelt. Entsteht dabei eine zweistellige Zahl, wird die Quersumme gebildet.
  • Alle so entstandenen Ziffern (einfach und verdoppelt) werden aufsummiert.
  • Die Prüfsumme ist gültig, wenn die Gesamtsumme ohne Rest durch 10 teilbar ist.
Wie kann man das mit Java-8-Streams implementieren? Dazu noch ohne Variablen, Zustandsänderungen und Fallunterscheidung? Beispielsweise so:

import java.util.PrimitiveIterator;
import java.util.stream.IntStream;

public class Luhn {

  public static boolean isValid(String number) {

    PrimitiveIterator.OfInt faktor =
      IntStream.iterate(1, i -> 3 - i).iterator();

    int summe = new StringBuilder(number).reverse()
      .toString().chars()
      .map(c -> c - '0')
      .map(i -> i * faktor.nextInt())
      .reduce(0, (a, b) -> a + b / 10 + b % 10);

    return (summe % 10) == 0;
  }
}

Als Beispiel durchlaufen wir isValid() mit dem String "8763".

Mit IntStream.iterate() erzeugen wir uns einen endlosen (aber lazy berechneten) Stream der Zahlen 1,2,1,2,1,2, ... – das ist der Faktor, mit dem jede Ziffer multipliziert wird.

Dann packen wir den Zahlen-String in einen StringBuilder, weil der eine reverse()-Methode bietet. Damit können wir die einzelnen Ziffern als chars()-Stream vorwärts durchlaufen (und müssen nicht von hinten zählen):

'3', '6', '7', '8'

Jedes Ziffern-char wir nun mit dem ersten map() auf den Ziffern-Wert 0..9 abgebildet und mit dem zweiten map() mit dem nächsten Faktor aus dem endlosen IntStream multipliziert:

3, 12, 7, 16

Mit reduce() berechnen wir nun die Quersumme aller dieser Zahlen, wobei wir für jede Zahl die Zehnerstelle (div 10) und die Einerstelle (mod 10) addieren, also

(0 + 3) + (1 + 2) + (0 + 7) + (1 + 6)

Ergibt 20, und das ist ohne Rest durch 10 teilbar. Somit ist die Prüfsumme gültig.


Mit Java 9 können wir uns das Umwandeln in einen String (und das Umkehren der Zeichenfolge) sparen, indem wir aus der zu prüfenden Zahl einen weiteren unendlichen Stream machen, den wir mit takeWhile() und einem geeigneten Lambda-Prädikat kappen:

import java.util.PrimitiveIterator;
import java.util.stream.IntStream;
import java.util.stream.LongStream;

public class Luhn9 {

  public static boolean isValid(long number) {

    PrimitiveIterator.OfInt faktor =
      IntStream.iterate(1, i -> 3 - i).iterator();

    long summe = LongStream.iterate(number, n -> n / 10)
      .takeWhile(n -> n > 0)
      .map(n -> n % 10)
      .map(i -> i * faktor.nextInt())
      .reduce(0, (a, b) -> a + b / 10 + b % 10);

    return (summe % 10) == 0;
  }
} 

Der chars()-Stream in der Java-8-Lösung war nach der Verarbeitung aller Zeichen zu Ende. LongStream.iterate() in dieser Java-9-Variante teilt die zu prüfende Zahl dagegen endlos immer weiter durch 10, also z.B.

8763, 876, 87, 8, 0, 0, 0, ...

Wir müssen den Stream also nur verarbeiten, solange die Zahl größer Null ist. takeWhile() macht genau das und liefert folgenden endlichen Stream:

8763, 876, 87, 8

Das erste map() liefert einen Stream der letzten Ziffern (mod 10):

3, 6, 7, 8

Der Rest (Multiplizieren mit dem Faktor und Quersummen-Addition) ist dann wieder wie bei der Java-8-Lösung.

Das ist vielleicht noch nicht perfekt funktional, weil wir keine Funktionskomposition beliebiger passender Funktionen durchführen können, sondern auf die vorhandene Stream-API angewiesen sind. Aber für Java-Bordmittel ist das doch gar nicht so schlecht.


Und wer noch nicht genug davon hat: Ein anderes schönes Beispiel für unendliche Java-Streams ist die Berechnung von Kaprekar-Zahlen, was kürzlich als Challenge auf dev.to lief. Neben takeWhile() kommt bei der Java-Lösung noch limit() zum Einsatz, um einen unendlichen Stream nicht mit einer Bedingung, sondern mit einer festen Anzahl zu begrenzen.

Montag, 13. März 2017

JSF-FacesContext in Unit-Tests mocken

Beim Schreiben von Unit-Tests für JSF-Beans kommt es leider ab und zu vor, dass der zu testende Code eine Fremdbibliothek verwendet, in der statisch auf
FacesContext.getCurrentInstance()
zugegriffen wird. Beim Ausführen des Tests kann dann eine NullPointerException im Code der Fremdbibliothek auftreten, die man nicht ohne Weiteres weg bekommt, denn der FacesContext hat zwar einen Getter, aber keinen Setter für das aktuelle Kontext-Objekt.

Hier kommt OmniFaces zu Hilfe – eine nützliche Hilfsbibliothek für JSF, die man vielleicht sowieso schon ins Projekt eingebunden hat. OmniFaces bietet die Methode Faces.setContext() an, mit der man den aktuellen JSF-Kontext setzen kann. Normalerweise benötigt man diese Methode nicht, aber in JSF-Unit-Tests kann sie Gold wert sein. Übergibt man z.B. ein Mockito-Mock-Objekt, ist man die NullPointerException schnell und einfach los:
import org.mockito.Mock;
import org.omnifaces.util.Faces;

public class FrontendBeanTest {

    @Mock
    private FacesContext facesContextMock;

    @Test
    public testFuerJsfBean {

        // Given
        Faces.setContext( facesContextMock );

        // When
        ...

        // Then
        ...
    }
}

Freitag, 10. März 2017

Firefox 52 und Java-Applets

Mit Version 52.0 hat Firefox die Unterstützung für Plugins eingestellt, die auf die Uralte NPAPI setzen – das betrifft auch das Java-Plugin und somit Java-Applets.

Das war schon länger angekündigt und ist absolut nachvollziehbar, denn die NPAPI hat diverse Sicherheitsprobleme. Zudem sind Java-Applets eine sterbende Technologie, und mit Java 9 wird das Applet-Browser-Plugin auf den Status deprecated gesetzt werden.

Manchmal ist man aber doch noch darauf angewiesen, dass man ein Applet im Browser aufrufen kann. Und das geht auch mit Firefox 52 noch:
  • Zum Einen kann man Firefox 52 ESR (Extended Support Release) verwenden. Darin wird der Plugin-Support bis Mai 2018 aktiviert bleiben.
  • Zum Anderen – und das ist vermutlich die schnellere Lösung – kann man die Firefox-Konfiguration anpassen. Dazu ruft man in der Adresszeile about:config auf, macht einen Rechtsklick in die Liste der Optionen und wählt im erscheinenden Popup Neu/Boolean auf. Als Namen gibt man plugin.load_flash_only an, als Wert false. Nach einem Browser-Neustart können Java-Applets dann wieder genutzt werden.
Und immer dran denken: Die Nutzung sowohl dieser Informationen als auch von Plugins wie Flash und Java-Applets geschieht vollkommen auf eigene Gefahr.