Donnerstag, Dezember 07, 2017

FlagArguments. Gut oder Böse?

Heute die Frage: Wie soll man mit Flag-Parametern umgehen? Flag-Parameter sind boolesche Methodenparameter, die die Ausführung einer Methode steuern. Dazu am besten gleich anschaulich ein Beispiel (Hier handelt es sich um eine fiktive Customer-Care-Relationship Anwendung. Kunden werden unterteilt in ‘Normalos’ und ‘Premium’ Kunden. Vorschläge von Premium Kunden landen in einer sogenannten Premium-Ablage. Normalos landen halt in der Normalo-Ablage):

public class ProposalServiceWithExtraParam {
    public void createProposal(Person person, boolean premiumCustomer) {
        Proposal proposal = new Proposal(person);
        if (premiumCustomer) {
            premiumQueue.add(proposal);
        } else {
            normalQueue.add(proposal);
        }
    }
}

Alternativ könnte man vielleicht auf den Extraparameter verzichten und dem API Benutzer eine weitere Methode zur Verfügung stellen?

public class ProposalServiceWithExtraMethod {
    public void createProposal(Person person) {
        Proposal proposal = new Proposal(person);
        premiumQueue.add(proposal);
    }

    public void createPremiumCustomerProposal(Person person) {
        Proposal proposal = new Proposal(person);
        normalQueue.add(proposal);
}

Jetzt geht es um die Frage, was ist besser? Extraparameter? Extramethode? Rein aus Gründen der Lesbarkeit ist die sprechende Variante mit der Extramethode, der Variante mit dem Extraparameter vorzuziehen.

Der Code, der diese API benutzen würde, könnte dann vermutlich folgendermaßen aussehen. Hier mit dem Extraparameter:

PersonService personService = ...; // Service für das Finden von Personen
CustomerService customerService = ...; // Service zur Beurteilung von Kunden
ProposalServiceWithExtraParam proposalService = new ProposalServiceWithExtraParam();
Person person = personService.findPerson(4711);

proposalService.createProposal(person, customerService.isPremiumCostumer(person));

Und jetzt mit der Extramethode:

ProposalServiceWithExtraMethod proposalService = new ProposalServiceWithExtraMethod();
Person person = personService.findPerson(4711);

if (customerService.isPremiumCostumer(person)) {
    proposalService.createPremiumCustomerProposal(person);
} else {
    proposalService.createProposal(person);
}

Bleiben wir bei dem Beispiel oben, könnte man vielleicht die kompaktere Variante mit dem Extraparameter vorziehen. Uneindeutiger wird die Sache in dem folgenden Beispiel:

ProposalServiceWithExtraParam proposalService = new ProposalServiceWithExtraParam();
Person person = personService.findPerson(4711);

proposalService.createProposal(person, customerService.bougthItems(person) > 10);

In diesem Fall wird der Premium-Status durch die Anzahl der gekauften Produkte ermittelt. Ist dieser größer als 10, wird ein Premium-Proposal erstellt. Jetzt ist es nicht mehr ganz so offensichtlich, was mit dem dritten Parameter gemeint ist. Umformuliert mit einem if-then-else und unter Verwendung der Extramethode erhält man folgenden Code:

ProposalServiceWithExtraMethod proposalService = new ProposalServiceWithExtraMethod();
Person person = personService.findPerson(4711);

if (customerService.bougthItems(person) > 10) {
    proposalService.createPremiumCustomerProposal(person);
} else {
    proposalService.createProposal(person);
}

Möglicherweise ist hier die Schreibweise mit der Extramethode für den Leser besser zu verstehen. Interessanterweise ist dieses Problem in anderen Sprachen nicht so dringend. In Javascript z.B. könnte man ein Parameterobjekt übergeben:

proposalService.createProposal({
    person: person,
    premiumCustomer: customerService.bougthItems(person) > 10
});

In Groovy können die Parameter ebenfalls benannt werden:

proposalService.createProposal(
    person: person,
    premiumCustomer: customerService.bougthItems(person) > 10
)

Ein klarer Vorteil für alle Sprachen, die benannte Methodenparameter erlauben.

Zu dem Thema haben sich einige Größen bereits Gedanken gemacht:

Mittwoch, November 15, 2017

Java 8 Collection Beispiel | Stream#collect()

Als Ergänzung zum letzten Blogeintrag heute der Kollege von Stream#reduce(): Stream#collect(): In den folgenden Überlegungen versuche ich herauszufinden, wie sich die beiden Methoden voneinander abgrenzen. D.h. wann sollte ich #reduce() verwenden, wann besser #collect()? Als erstes ein Blick in die Interface Definition der beiden Methoden.

interface Stream<T> {
    // Die verschiedenen reduce(...) Methoden
    Optional<T> reduce(BinaryOperator<T> accumulator);
              T reduce(T identity,
                       BinaryOperator<T> accumulator);
          <U> U reduce(U identity,
                       BiFunction<U, ? super T, U> accumulator,
                       BinaryOperator<U> combiner);

    // Die verschiedenen collect(...) Methoden
          <U> U collect(Supplier<U> supplier,
                        BiConsumer<U, ? super T> accumulator,
                        BiConsumer<U, U> combiner);
       <R, A> R collect(Collector<? super T, A, R> collector);
}

Gemeinsamer Parameter in allen Methodendefinition ist der accumulator. In der reduce Variante ist der accumulator eine BiFunction und liefert ein Ergebnis zurück. In der collect Variante wird für den accumulator ein BiConsumer übergeben. Dieser liefert kein Ergebnis zurück. Eine weitere Gemeinsamkeit findet sich im Parameter combiner, welcher im Falle der parallelen Abarbeitung eines Streams zur Anwendung kommt.

Bei mir drängt sich die Frage auf, wann benutze ich welche Methode. Gibt es dafür eine Regel? Einen Regelsatz oder zumindest eine Faustformel? Bei meinem Streifzug durch das Internet konnte ich keine definitiven Regeln finden. Dafür aber allerhand Beispiele für reduce und collect. Zu erst ein Beispiel für reduce:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
assertThat(numbers.stream().reduce((n, m) -> n + m).get()).isEqualTo(55);

Falls identity = 0 angenommen werden kann, dann kann man auf die Optional#get() Abfrage verzichten.

assertThat(numbers.stream().reduce(0, (n, m) -> n + m)).isEqualTo(55);
assertThat(numbers.stream().reduce(0, Integer::sum)).isEqualTo(55);

Wie würde das mit collect aussehen?

assertThat(numbers.stream().collect(
    Collectors.summingInt(n -> n)).intValue()).isEqualTo(55);

Für die collect Methode bietet Java einige nützliche Utilities in java.util.stream.Collectors an (Siehe Java API Collectors). Eine dieser nützlichen Helferlein ist, wie oben gesehen, z.B. Collectors#summingXxx(). Im Vergleich zu dem reduce Beispiel wirkt die collect-Variante etwas umständlich. Ich würde hier die reduce Variante vorziehen. Das Ergebnis ist das Gleiche.

Falls man die Sache ausschreibt (also nicht die Helfer aus der Klasse Collectors verwendet) und nicht wie Collectors.summingInt() intern mit einem Array arbeitet, dann sieht die Sache für collect folgendermaßen aus (Das ist wirklich keine schöne Lösung, zeigt aber, wo die Schwäche bzw. dann auch die Stärke von collect liegt): Zunächst benötigt man eine Ablage für die Akkumulationsergebnisse, da der BiConsumer kein Ergebnis zurückliefert. Ich nenne die Zwischenablage hier IntHolder:

private class IntHolder {
    private int value;
    public IntHolder(int value) {
        this.value = value;
    }
    public void setValue(int value) {
        this.value = value;
    }
    public int getValue() {
        return value;
    }
}

Im Anschluss kann collect() mit den Parametern gefüttert werden.

IntHolder intHolder = numbers.stream().collect(() -> {
    return new IntHolder(0);
}, (l, v) -> {
    l.setValue(l.getValue() + v);
}, (l, m) -> {
    l.setValue(l.getValue() + m.getValue());
});
assertThat(intHolder.getValue()).isEqualTo(55);

Durch die Verwendung eines IntHolder wird die Implementierung aufgebläht. Reduce kann in diesem Fall eindeutig mit einer kompakteren Schreibweise punkten. Hier noch drei Alternativen für das Summieren von Integers aus einem Stream:

assertThat(numbers.stream().mapToInt(i -> i.intValue()).sum()).isEqualTo(55);
assertThat(numbers.stream().mapToInt(Integer::intValue).sum()).isEqualTo(55);
assertThat(numbers.stream().mapToInt(i -> i).sum()).isEqualTo(55);

Statt int-Werte zu addieren, versuche ich es nun einmal mit String Werten.

final String STRING = "abcdefghi";
List<String> strings = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i");
assertThat(strings.stream().reduce(
        "",
        (a, b) -> a + b)).isEqualTo(STRING); // (1)
assertThat(strings.stream().reduce(
        new StringBuilder(),
        StringBuilder::append,
        StringBuilder::append).toString()).isEqualTo(STRING); // (2)

Hier zeigt sich der Nachteil von reduce. In Beispiel (1) wird in jedem Akkumulationsschritt ein neuer String erzeugt. Das ist hinglänglich bekannt, dass dies in Java aus Performance-Sicht keine gute Idee ist. In Beispiel (2) wird der StringBuilder verwendet. Das Beispiel kann funktionieren, so lange der Stream nicht parallel abgearbeitet wird.

final String STRING = "abcdefghijklmnopqrstuvwxyz";
List<String> strings = Arrays.asList(
    "abc", "def", "ghi", "jkl", "mno", "pqr", "stu", "vwx", "yz");
assertThat(strings.parallelStream().reduce(
        new StringBuilder(),
        StringBuilder::append,
        StringBuilder::append).toString()).isNotEqualTo(STRING); (1)

Mit collect könnte man folgendes formulieren:

assertThat(strings.stream().collect(
        () -> new StringBuilder(),
        (a, b) -> a.append(b),
        (a, b) -> a.append(b)).toString()).isEqualTo(STRING); // (2)
assertThat(strings.stream().collect(
        () -> new StringBuilder(),
        StringBuilder::append,
        StringBuilder::append).toString()).isEqualTo(STRING); // (3)        
assertThat(strings.stream().collect(Collectors.joining())).isEqualTo(STRING); // (4)

Die Beispiele (1) und (3) sehen frappierend ähnlich aus. Man kann leicht übersehen, dass in dem einen Fall #reduce() verwendet wird, in dem anderen Fall #collect(). Die Variante mit collect funktioniert allerdings korrekt mit parallelen Streams. Am einfachsten ist Variante (4). Hier wird die Utility Klasse Collectors verwendet.

Ja und nun? Welche Schlussfolgerung kann man ziehen?

Der Unterschied zeigt sich im Akkumulator. #reduce() führt mit der BiFunction als Akkumulator eine funktionale Reduktion aus. #collect() ändert mit dem BiConsumer einen existierenden Wert und arbeitet somit nicht seiteneffektfrei. Das ist laut der Javadoc von BiConsumer auch nicht gefordert. Die Vereinbarung gilt für alle Varianten von java.util.function.Consumer. (Das kann bei der parallelen Verarbeitung eines Streams eventuell ein Problem sein). #reduce() ist dann im Vorteil, wenn die Zwischenergebnisse von accumulator ohne große Performance Verluste angelegt werden können. Zum Beispiel sind die einfachen numerischen Datentypen dafür sehr gut geeignet. D.h. habe ich einen Akkumulator, der einen numerischen Datentyp erzeugt, dann ist vermutlich #reduce() der geeignetere Kandidat. Sind (komplexe) Objekte das Zwischenergebnis (z.B. String wie oben), ist möglicherweise #collect() die bessere Wahl. Von der Definition her bietet #reduce() die größte Sicherheit bei der Verarbeitung eines parallelen Streams. Aber diese Sicherheit kann trügerisch sein:

List<Integer> numbers = Arrays.asList(
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20);
assertThat(numbers.stream().reduce(0, (n, m) -> n - m).intValue()).isEqualTo(-210);
assertThat(numbers.parallelStream().reduce(0, (n, m) -> n - m).intValue()).isNotEqualTo(-210);

Interessant ist vielleicht eine kurze Betrachtung der Laufzeiten von reduce und collect für das Summieren von Integer-Werten.

package misc;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.joda.time.DateTime;
import org.junit.Test;

public class ReduceVsCollectRuntime {

    @Test
    public void joiningSomeStrings() {
        List<Integer> integers = integers(4_000_000);

        long runtimeWithReduce = 0;
        long runtimeWithCollect = 0;
        long runtimeWithCollectors = 0;

        runtimeWithReduce = runtime(
            integers.stream(), ReduceVsCollectRuntime::joinWithReduce);
        runtimeWithCollect = runtime(
            integers.stream(), ReduceVsCollectRuntime::joinWithCollect);
        runtimeWithCollectors = runtime(
            integers.stream(), ReduceVsCollectRuntime::joinWithCollectors);

        System.out.println("Reduce Millis: " + runtimeWithReduce);
        System.out.println("Collect Millis: " + runtimeWithCollect);
        System.out.println("Collectors Millis: " + runtimeWithCollectors);

        runtimeWithReduce = runtime(
            integers.parallelStream(), ReduceVsCollectRuntime::joinWithReduce);
        runtimeWithCollect = runtime(
            integers.parallelStream(), ReduceVsCollectRuntime::joinWithCollect);
        runtimeWithCollectors = runtime(
            integers.parallelStream(), ReduceVsCollectRuntime::joinWithCollectors);

        System.out.println("Parallel Reduce Millis: " + runtimeWithReduce);
        System.out.println("Parallel Collect Millis: " + runtimeWithCollect);
        System.out.println("Parallel CollectorsMillis: " + runtimeWithCollectors);
    }

    public static Long joinWithReduce(Stream<Integer> stream) {
        return stream.reduce(
                0L,
                (n, m) -> n.longValue() + m.longValue(),
                (n, m) -> n.longValue() + m.longValue());
    }

    public static Long joinWithCollect(Stream<Integer> stream) {
        return stream.collect(
                () -> new long[1],
                (n, m) -> n[0] += m.longValue(),
                (n, m) -> n[0] += m[0])[0];
    }

    public static Long joinWithCollectors(Stream<Integer> stream) {
        return stream.collect(Collectors.summingLong(i -> i));
    }

    private long runtime(Stream<Integer> stream, Function<Stream<Integer>, Long> streamFunction) {
        DateTime start = DateTime.now();
        Long joinReduce = streamFunction.apply(stream);
        System.out.println("Sum: " + joinReduce);
        DateTime end = DateTime.now();
        return end.getMillis() - start.getMillis();
    }

    private List<Integer> integers(int nums) {
        Random random = new Random();
        List<Integer> integers = new ArrayList<>();
        for (int i = 0; i < nums; i++) {
            integers.add(random.nextInt());
        }
        return integers;
    }

}

Und das ist das Ergebnis:

Sum: 4832884415034
Sum: 4832884415034
Sum: 4832884415034
Reduce Millis: 171
Collect Millis: 16
Collectors Millis: 31
Sum: 4832884415034
Sum: 4832884415034
Sum: 4832884415034
Parallel Reduce Millis: 62
Parallel Collect Millis: 0
Parallel Collectors Millis: 16

In den getesteten Fällen schneidet #collect() jeweils am besten ab (Gemessen auf einem i5-4570 CPU 3.2GHz). Also gewinnt immer #collect()?


Weitere Links:

Allgemeine Informationen zu Lambdas, Functions, Streams, etc.:

Freitag, September 01, 2017

Blogger und Markdown

Leider versteht der Blogger (also blogger.com) Editor kein Markdown. Zum Glück gibt es aber online Editoren, die dieses Format beherrschen und aus Markdown HTML generieren können. Hier sind ein paar online Editoren, die diese Funktion beherrschen:
  • Dillinger hat eine nette Oberfläche mit einem Preview Bereich. Neben einem HTML Export werden verschiedene andere Formate angeboten (z.B. PDF).
  • markdown-to-html liefert ein schlichtes Textfeld für die Konvertierung.
  • stackeditor mit einer ähnlich guten Oberfläche wie Dillinger. Unterstützt ebenfalls andere Formate.
  • markable.in ist erst eine Anmeldung erforderlich bevor es mit dem Tippen losgehen kann.
  • Bei ctrlshift sieht es altbacken aus, erfüllt aber seinen Zweck.
Vielleicht komme ich um eine eigene Jekyll Installation doch noch rum.

Freitag, Juni 16, 2017

Java 8 Collection Beispiele | Stream#reduce()

Thema heute: Die unterschiedlichen #reduce(...) Methoden der Klasse java.util.Stream. Betrachtet werden die Methoden:

  1. Optional<T> java.util.Stream#reduce(BinaryOperator<T> accumulator)
    
  2. T java.util.Stream#reduce(T identity,
                              BinaryOperator<T> accumulator)
    
  3. <U> U java.util.Stream#reduce(U identity,
                                  BiFunction<U,? super T, U> accumulator,
                                  BinaryOperator<U> combiner)

Gemein ist allen drei Varianten der Parameter accumulator. In den ersten beiden Varianten ist der accumulator vom Typ BinaryOperator<T>. Der BinaryOperator<T> nimmt zwei Werte entgegen und liefert ein Ergebnis zurück. Die beiden Eingabeparameter und der Rückgabewert sind vom gleichen Typ und entsprechen dem Typ des Streams. Damit sind diese beiden Varianten zu verwenden, wenn es um z.B. um die Addition von Integer Werten aus einem Stream geht. Also immer dann, wenn das Akkumulationsergebnis vom gleichen Typ ist wie der Stream.

Sollen die Längen der Strings aus einem Stream addiert werden, kommt die dritte Ausprägung ins Spiel: Hier entspricht der accumulator einer BiFunction<U,? super T, U>. Der zweite Parameter ist ein Element aus dem Stream. Der erste Parameter das Ergebnis aus der letzten Akkumulation. Entsprechend dem Beispiel wäre also der erste Parameter ein Integer und der zweite Parameter ein String. Das Ergebnis ist dann vom Typ Integer. Für das erste Element aus dem Stream wird bei Anwendung der Akkumulation als erster Parameter (Erinnerung: Die Summe aller String-Längen) identity verwendet. Für das Beispiel wäre dies sinnvollerweise die 0. Der dritte Parameter combiner wird bei einer parallelen Verarbeitung des Streams benötigt. In den ersten beiden Varianten kann der Akkumulator als Combiner verwendet werden, da Eingabe- und Ausgabetyp die gleichen sind. Um bei dem Beispiel zu bleiben: Zwei Threads teilen sich die Bearbeitung des Streams. Es werden zwei Integer Werte ermittelt, die jeweils die Länge der Strings aus dem jeweiligen Teil-Stream repräsentieren. Diese beiden Integer Werte werden vom Combiner addiert.

Mathematisch ausgedrückt ist es vielleicht am anschaulichsten. Der zentrale Parameter accumulator repräsentiert eine Funktion (In der Java Welt ist es entweder ein java.util.function.BinaryOperator oder ein java.util.function.BiFunction) zur Berechnung der Reduktion. Allen drei Methoden ist dieser Parameter gemein. Es sei f() eine Funktion. f() wird auf alle Elemente des Stream rekursiv angewendet.

f(f(f(a0, a1), a2), a3)
Ein andere Darstellung ergibt sich, wenn statt einer Funktion ein (Bi-)Operator op verwendet wird.
a0 op a1 op a2 ...
bzw. ganz allgemein
ai op ai + 1

Die binäre Funktion/der binäre Operator sollte folgenden funktionalen Anforderungen genügen:

  • assoziativ: a op (b op c) = (a op b) op c. Darunter fallen solche Operationen wie die Addition, Multiplikation, Min, Max. Die Subtraktion ist z.B. nicht assoziativ: (20-5)-4 ≠ 20-(5-4). Das gleiche gilt für die Division. Eine Stolperfalle für (hastige) Codeänderungen?
  • Nicht störend (non-interfering). Wie kann man das verstehen? Auf "NOSID-Java 8 stream reduce vs collect" findet sich eine gute Erklärung: Die reduce-Methode eignet sich ausschließlich für Reduktionen mit reinen Funktionen. Damit sind Funktionen gemeint, die beim Aufruf weder ihren eigenen Zustand, den Zustand der Übergabeparameter noch irgendwelchen global sichtbaren Zustand ändern. Sie geben lediglich eine Referenz auf ein Objekt zurück, das üblicherweise entweder neu erzeugt wird oder zumindest unveränderlich ist – in jedem Fall aber keine Auswirkungen auf den sonstigen Programmzustand hat.
  • zustandslos. Der accumulator darf keinen inneren, äußeren oder globalen Zustand besitzen. Passt irgendwie gut zu dem letzten Punkt.
Folgt man diesen Anforderungen erhält man eine im mathematischen Sinne echte Funktion.


Fall 1

Optional<T> java.util.Stream#reduce(BinaryOperator<T> accumulator)

Für die #reduce(...) aus 1 ergeben sich folgende Regeln für die Ergebnisberechnung:

  • Leere Streams erzeugen ein leeres Ergebnis. Deswegen wird ein Optional als Rückgabeparameter verwendet.
  • Ein 1-elementiger Stream liefert das erste und einzige Element zurück. Die Akkumulator Funktion wird auf das Element nicht angewendet!
  • Auf mehrelementige Streams wird die BinaryOperator Funktion, wie beschrieben, angewendet.


Fall 2

T java.util.Stream#reduce(T identity, BinaryOperator<T> accumulator)

Das #reduce(...) aus 2 bekommt einen weiteren Parameter identity. Mathematisch erfüllt es den folgenden Zweck: a op id = a. Für die Addition wäre das z.B. die 0 oder für die Multiplikation die 1.

  • Leere Streams liefern die identity zurück. Im Gegensatz zu 1 kann das Optional als Rückgabeparameter entfallen.
  • Für einen einelementigen Stream wird der Akkumulator mit den Parametern identity und dem Stream-Element aufgerufen.
  • Auf mehrelementige Streams wird die BinaryOperator Funktion, wie beschrieben, angewendet. Die erste Akkumulation wird mit Hilfe der identity berechnet.


Fall 3

<U> U java.util.Stream#reduce(U identity,
                              BiFunction<U,? super T, U> accumulator,
                              BinaryOperator<U> combiner)

Dieser Fall wird benötigt, wenn der accumulator in der einfachen Form BinaryOperator<T> nicht verwendet werden kann. Siehe dazu oben in der Einleitung (Berechnung der Gesamtlänge aller Strings eines Streams).

  • Leere Streams liefern die identity zurück. Im Gegensatz zu 1 kann das Optional als Rückgabeparameter entfallen.
  • Für einen einelementigen Stream wird der Akkumulator mit den Parametern identity und dem Stream-Element aufgerufen. Der Rückgabetyp der Akkumulation ist ungleich dem Stream-Typ.
  • Mehrelementige Streams werden wie aus Fall 2 bekannt verarbeitet. Falls der Stream für die Parallelverarbeitung aufgeteilt wird, kommt der combiner ins Spiel. Dieser sorgt dafür, das die Teilergebnisse zu einem Gesamtergebnis kombiniert werden. Für die Fälle 1 und 2 kann der Akkumulator für die Zusammenführung der Teilergebnisse selbst verwendet werden. Die Parametertypen für ein Ein- und Ausgabe sind hier gleich. Laut Javadoc wird die folgende Anforderung an einen combiner gestellt:
    combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)
    Was heißt das? Egal, ob der Combiner bei der Verarbeitung des Streams verwendet wird oder nicht, das Endergebnis der Stream-Reduktion/Akkumulation muss gleich sein.


Nach der ganzen Theorie jetzt ein wenig Code zum ausprobieren (In der Theorie sind Theorie und Praxis gleich):
import org.assertj.core.api.Assertions.*;
import org.junit.*;

List strings = Arrays.asList("Abc", "Def", "Ghi", "Jkl", "Mno");

assertThat(strings.stream().reduce(0,
        (Integer sum, String string) -> sum + string.length(),
        (Integer sum1, Integer sum2) -> sum1 + sum2).intValue()).isEqualTo(15);

Nach der ganzen Theorie nun einige Beispiele. In diesem Fall werden die Elemente einer Liste addiert. Ich empfehle, den Code per Copy und Paste in die bevorzugte IDE zu übernehmen und den Test einmal auszuführen. Das assertThat() stammt aus dem Package org.assertj.core.api.Assertions.*. Der Rest ist wohlbekannt.

assertThat(
    Arrays.asList(10, 20, 30)
          .stream()
          .reduce((result, element) -> result + element)
          .get())
          .isEqualTo(60);

assertThat(
    Arrays.asList(10)
          .stream()
          .reduce((result, element) -> result + element)
          .get())
          .isEqualTo(10);

// Bei der Addition zweier Elemente soll das Ergebnis um 1 erhöht
// werden.
assertThat(
    Arrays.asList(10, 20, 30)
          .stream()
          .reduce((result, element) -> result + element + 1)
          .get())
          .isEqualTo(62);
// Auf das erste Element wird die übergebene Funktion nicht angewendet!

assertThat(
    Arrays.asList(10)
          .stream()
          .reduce((result, element) -> result + element + 1)
          .get())
         .isEqualTo(10);
// Auf das erste Element wird die übergebene Funktion nicht angewendet!

// Nun zusätzlich mit dem Parameter identity
assertThat(
    Arrays.asList(10, 20, 30)
          .stream()
          .reduce(0, (result, element) -> result + element + 1))
          .isEqualTo(63);
// Die Funktion wird auf das erste Element angewendet. In diesem Fall
// wird der result Parameter mit dem identity Parameter vorbelegt.

assertThat(
    Arrays.asList(10)
         .stream()
         .reduce(0, (result, element) -> result + element + 1))
         .isEqualTo(11);

assertThat(
    Arrays.asList(666)
         .stream()
         .reduce(5, (result, element) -> result + element))
         .isEqualTo(671);
// 666 + 5 => 671


// Ein Beispiel mit Combiner:
assertThat(Arrays.asList("Abc", "Def", "Ghi", "Jkl", "Mno")
         .stream()
         .reduce(0, (Integer sum, String string) -> sum + string.length(),
                    (Integer sum1, Integer sum2) -> sum1 + sum2)
         .intValue())
         .isEqualTo(15);

// Tückisch ...
List strings = Arrays.asList("abc", "def", "ghi", "jkl", "mno", "pqr", "stu", "vwx", "yz");

// ... Sequentiell ausgefuehrt wäre das noch in Ordnung ...
assertThat(
    strings.stream().reduce(
        new StringBuilder(),
        StringBuilder::append,
        StringBuilder::append).toString()).isEqualTo("abcdefghijklmnopqrstuvwxyz");

// ... die parallele Streamverarbeitung verursacht aber einen Fehler ...
assertThat(
    strings.parallelStream().reduce(
        new StringBuilder(),
        StringBuilder::append,
        StringBuilder::append).toString()).isNotEqualTo("abcdefghijklmnopqrstuvwxyz");
Gerade letztes Beispiel zeigt, dass die naive Verwendung von reduce interessante Fehler verursachen kann. Problematisch in diesem Fall ist die Verwendung von StringBuilder::append, welches ein berechnetes Ergebnis verändert. Das fällt in die Kategorie interfering (störend beeinflussend, einmischend). Die Codebeispiele finden sich auch auf GitHub unter Java 8 Examples

Weitere Hinweise und Beispiele:



UPDATE:

  • 2017-08-15 Was ist 'interfering'? Abgrenzung im Vergleich zu #collect()

Donnerstag, Juni 15, 2017

Visual Code | Typescript

Typescript ist die Pflichtsprache für das neue Angular2! Anfänglich war ich skeptisch, ob es sich wirklich lohnt für ein GUI Framework eine neue Sprache zu lernen. Nach einigem ausprobieren, hat mich das Konzept überzeugt. Besonders angetan hat es mir die Typsicherheit. Wer schon mal in einem mittelgroßen Projekt mit unzähligen Klassen und (externen) Schnittstellen mit Javascript programmiert hat, kann das vermutlich verstehen. Als Editor verwende ich VisualCode. Ein Produkt aus dem Hause Microsoft. Kostenlos runterzuladen. Mit einem schicken, schlanken Frontend. Sozusagen die zweite Überraschung. Atom war bisher mein Favorit. Im Zusammenspiel mit Typescript finde ich VisualCode etwas eleganter. Diese Feststellung beruht nicht auf Fakten und wurde auf der Gefühlsebene entschieden.

Hier meine ersten Änderungen an den Default-Einstellungen von VisualCode. Die Einstellungen sind übrigens in JSON Dateien hinterlegt. Im Menü gelangt man über File > Preferences > Settings auf die gewünschte Datei.

Falls für das Web-Projekt nur die *.ts Dateien relevant sind, können die aus den Typescript generierten Javascript Dateien stören. In VisualCode kann eine Einstellung vorgenommen werden, dass der Editor alle Javascript Dateien ignoriert, die mit dem gleichen Prefix beginnen. Beispiel: start.ts und start.js. In der Explorer-Ansicht wird die start.js Datei nicht angezeigt. Die Einstellung kann systemweit oder nur für einen Arbeitsbereich gelten.

{
    "files.exclude": {
        // include the defaults from VS Code
        "**/.git": true,
        "**/.DS_Store": true,

        // exclude .js and .js.map files, when in a TypeScript project
        "**/*.js": { "when": "$(basename).ts"},
        "**/*.js.map": true
    }
}

Mit dem Mai Update verfügt der Editor über eine Minimap. Diese nimmt mir allerdings zuviel Platz weg. Mit der folgenden Einstellung kann man sie deaktivieren:

"editor.minimap.enabled": false

Freitag, April 21, 2017

Links der Woche (16. Woche 2017)

  • Eine DSL zur Formulierung von Datenbankabfragen mit Java: Query DSL
  • Ein Tool zur Überwachung von Servern: Prometheus
  • Falls ihr ab und an mal eine SD Karten bespielt. Hier gibt es ein hübsches, visuelles Tool dazu: Etcher.io
  • Google bietet eine Bibliothek für das Abfragen von Captchas im Web an: reCaptcha Der Algorithmus hinter reCaptcha versucht zu erkennen, dass das Gegenüber ein Mensch ist. In diesem Fall wird kein Captcha eingeblendet.
  • Ein Tool zur Verwaltung von Geheimnissen und Schlüsseln: Vaultproject.io
  • Für eure Mindstorm Roboter gibt es eine neue Firmware: ev3dev.org
  • Momentan bin ich auf der Suche nach einer neuen Blogger Software. Positiv aufgefallen ist mir jekyll. Das Tool findet ebenfalls auf Github Verwendung und generiert die Webseiten zu den Projekten. Siehe auch pages.github.com
  • Auf Heise gibt es ein Video mit ein paar Tipps für den Umgang mit Docker: 5 Docker Tipp
  • Von Mockito gibt es mittlerweile einen 2er Release Branch: Mockito 2.x.x In der 2er Linie wird kein mockito-all mehr angeboten. Also mockito-all durch mockito-core ersetzen und dann mal sehen was fehlt.
  • PhantomJS steht auf dem Abstellgleis. Siehe PhantomJS. Der Maintainer zieht sich zurück. Ursache sind die angekündigten, sogenannten Headless-Versionen, von Chrome und Mozilla, die PhantomJS somit obsolet erscheinen lassen. Nähere Informationen finden sich unter dem Link oben.
  • Zu guter letzt einen Blog: blog.Schauderhaft.de

Donnerstag, April 20, 2017

Oracle SQL | Group by Date

Das folgende Oracle-SQL gruppiert die fehlgeschlagenen Datensätze aus der Tabelle 'tabelle_mit_datum' nach Jahr, Monat und Tag:
SELECT
    COUNT(*) AnzahlFehler,
    TO_CHAR(cs.datum, 'YYYY') Jahr,
    TO_CHAR(cs.datum, 'MM') Monat,
    TO_CHAR(cs.datum, 'DD') Tag,
FROM
    tabelle_mit_datum tmd
WHERE
    tmd.status = 'Fehler'
    AND tmd.datum BETWEEN
        TO_DATE('01.01.2017', 'DD.MM.YYYY')
        AND TO_DATE('30.04.2017', 'DD.MM.YYYY')
GROUP BY
    TO_CHAR(cs.datum, 'YYYY') Jahr,
    TO_CHAR(cs.datum, 'MM') Monat,
    TO_CHAR(cs.datum, 'DD') Tag,
ORDER BY
    TO_CHAR(cs.datum, 'YYYY') Jahr DESC,
    TO_CHAR(cs.datum, 'MM') Monat DESC,
    TO_CHAR(cs.datum, 'DD') Tag DESC
;
Oracles TO_CHAR(date, dateformat) Funktion kann einige tolle Sachen. Der folgende Ausdruck liefert die Woche des Jahres zurück:
SELECT TO_CHAR(sysdate, 'IW') FROM dual;
Folgende Formate stehen als Parameter zur Verfügung:
YEARYear, spelled out
YYYY4-digit year
YYYLast 3 digits of year.
YYLast 2 digits of year.
YLast digit of year.
IYYLast 3 digits of ISO year.
IYLast 2 digits of ISO year.
ILast 1 digit of ISO year.
IYYY4-digit year based on the ISO standard
QQuarter of year (1, 2, 3, 4; JAN-MAR = 1).
MMMonth (01-12; JAN = 01).
MONAbbreviated name of month.
MONTHName of month, padded with blanks to length of 9 characters.
RMRoman numeral month (I-XII; JAN = I).
WWWeek of year (1-53) where week 1 starts on the first day of the year and continues to the seventh day of the year.
WWeek of month (1-5) where week 1 starts on the first day of the month and ends on the seventh.
IWWeek of year (1-52 or 1-53) based on the ISO standard.
DDay of week (1-7).
DAYName of day.
DDDay of month (1-31).
DDDDay of year (1-366).
DYAbbreviated name of day.
JJulian day; the number of days since January 1, 4712 BC.
HHHour of day (1-12).
HH12Hour of day (1-12).
HH24Hour of day (0-23).
MIMinute (0-59).
SSSecond (0-59).
SSSSSSeconds past midnight (0-86399).
FFFractional seconds.

Mittwoch, Februar 22, 2017

GIT Aliase für das Stashing

Git´s stash Befehl ist immer dann hilfreich, wenn die aktuellen Arbeiten am Sourcecode kurzfristig unterbrochen werden müssen. Oder wenn die eingeschlagene Codeänderung (wieder?) auf einen Holzweg führt und man die Änderungen verwerfen möchte (wo ein wegwerfen vielleicht angebrachter wäre?). In der aktuellen GIT Version (2.11.1) gibt es einige neue Parameter für das Stashen.

git config --global alias.stsh 'stash --keep-index'
git stsh
Stash'd nur die geänderten, nicht ge-stage'ten und dem Repository bekannten Dateien. D.h. geänderte und mit 'git add' der Staging-Area hinzugefügte Dateien, werden nicht auf den Stash geschoben.
git stash
Das Default-Stash. Wie gehabt. (Siehe GIT Doku).
git config --global alias.staash 'stash --include-untracked'
git staash
Stash'd alle dem Repository bekannten und nicht bekannten Dateien. Inklusive der Dateien in der Staging-Area.
git config --global alias.staaash 'stash --all'
git staaash
Stash'd alle dem Repository bekannten und nicht bekannten Dateien. Inklusive der Dateien in der Staging-Area. Zusätzlich werden alle Dateien aus der Ignore-List ebenfalls in den Stash mit aufgenommen.

Zusammenfassung

git config --global alias.stsh 'stash --keep-index'
git config --global alias.staash 'stash --include-untracked'
git config --global alias.staaash 'stash --all'
git config --global alias.shorty 'status --short --branch'

Gefunden auf dzone- lesser known git commands.

Mittwoch, Januar 18, 2017

Eclipse Shortcuts

Meine Eclipse Shortcuts. Eine kurze Aufzählung. Und an und ab gibt es mal eine Ergänzung.
F12               Wechselt in den Editor
Ctrl F6           Next View (Ich verlege den Key auf Alt-Q)
Ctrl F7           Schnellauswahl für den Wechsel der View
Ctrl Q            Wechselt zu der letzten editierten Stelle
Ctrl Alt H        Anzeige der Aufruf-Hierarchie einer Methode
Ctrl T            Zeigt die Vererbungshierarchie
Ctrl L            Springt zur Zeilennummer n
Ctrl E            Öffnet ein Popup für die Schnellauswahl eines Editors
Ctrl O            Öffnet ein Popup für die Schnellauswahl einer Methode
Ctrl 1            Quick Fix
Ctrl 3            Quick Access (Suche für Eclipse Aktionen)
Ctrl <Space>      Code-Vorschläge/-Assistent
Ctrl Shift F      Formatiert den Source Code
Ctrl Shift O      Organisiert die Import Statements
Ctrl Shift G      Findet alle Referenzen zu einer Klasse

Alt <crsr up>     Verschiebt die aktuelle Code-Zeile nach oben
Alt <crsr down>   Verschiebt die aktuelle Code-Zeile nach unten
Alt <crsr left>   Wechselt das Tab innerhalb des Editors nach links
Alt <crsr right>  Wechselt das Tab innerhalb des Editors nach rechts
Ctrl <page up>    Wechselt in den zuvor geöffneten Editor
Ctrl <page down>  Wechselt in den nächsten Editor

Alt Shift X       Ausführen einer Aufgabe
Alt Shift X T     Ausführen der JUnit Tests der aktuell ausgewaehlten Klasse
Alt Shift Q       Auswählen einer View
Alt Shift Q S     Öffnet das Suchen Fenster

AssertJ und java.util.List

AssertJ hat eine praktische Möglichkeit, Listen in JUnit Tests abzuprüfen. Insbesondere, wenn in der Liste komplexe Objekte abgelegt sind, s...