Und wieder mal die Damen…

Ja, ich weiß, ich habe versprochen, die 8 Damen in Ruhe zu lassen. Aber als das geschrieben hatte, war funktionale Programmierung in Java so unbequem, dass ich nicht gedacht hätte, dass man auch nur ansatzweise einmal eine Übersetzung aus Haskell wagen kann. Zur Erinnerung, das hier was mein Ausgangspunkt:

nqueens :: Int -> [[(Int,Int)]] 
nqueens n = foldr qu [[]] [1..n]
    where qu k qss = [ ((j,k):qs) | qs <- qss, j <- [1..n], all (safe (j,k)) qs ]
          safe (j,k) (l,m) = j /= l && k /= m && abs (j-l) /= abs (k-m)

Und das hier war meine Scala-Version:

object queens {
   def nqueens(n: Int) = {
      import math.abs
      type Pos = (Int, Int)
      def safe(p:pos, q:pos) = p._1 != q._1 && p._2 != q._2 && 
                               abs(p._1 - q._1) != abs(p._2 - q._2)
      def qu(k: Int, qss:List[List[Pos]]) =
        for(qs <- qss; j <- (1 to n) if qs.forall(safe(_ ,(j,k)))) yield ((j,k) :: qs)
      (1 to n).foldRight(List(List[Pos]()))(qu)
   }
   def main(args:Array[String]) = println(nqueens(8).mkString("\n"))
}

Die folgende Java 8-Version ist beiliebe nicht allein auf meinem eigenen Mist gewachsen. Ich habe nämlich feststellen müssen, dass meine Kentnisse über die javanischen Lambdas zwar recht gut sind, bei der Stream-API aber doch noch einige Bildungslücken vorhanden sind. Danke an alle “Mitwirkenden” :-)

Zuerst einmal brauchen wir ein Äquivalent zu Pos, wie gewohnt etwas länglicher in Java:

public class Pos {
    public final int x;
    public final int y;

    public Pos(int x, int y) {
        this.x = x;
        this.y = y;
    }
    public String toString() {
        return String.format("(%d,%d)",x,y);
    }
}

Nun die eigentliche Berechnung, die strukturell immmer noch recht dicht am Original liegt – nur auf mehrere Methoden verteilt und mit etwas sprechenderen Bezeichnern:

import static java.lang.Math.abs;
 
import java.util.*;
import java.util.stream.*;
 
public class Queens {

    private static boolean safe(Pos p, Pos q) {
        return p.x != q.x && p.y != q.y && 
            abs(p.x - q.x) != abs(p.y - q.y);
    }

    private static <T> List<T> snoc(List<T> ts, T t) {
        List<T> result = new LinkedList<>(ts);
        result.add(t);
        return result;
    }

    private static Stream<Integer> range(int fromInclusive, int toInclusive) {
        return IntStream.rangeClosed(fromInclusive, toInclusive).boxed();
    }

    private static Stream<List<Pos>> solveRow(int row, int boardSize, 
                                              Stream<List<Pos>> solutions) {
        return solutions.flatMap(solution ->
            range(1, boardSize).flatMap(column ->
                solution.stream().allMatch(pos ->
                    safe(pos, new Pos(row, column)))
                    ? Stream.of(snoc(solution, new Pos(row, column)))
                    : Stream.empty()));
    }

    public static Stream<List<Pos>> nqueens(int boardSize) {
        return range(1, boardSize).reduce(
            Stream.of(Collections.emptyList()),
            (solutions, row) -> solveRow(row, boardSize, solutions),
            Stream::concat);
    }

    public static void main(String[] args) {
        nqueens(8).forEach(System.out::println);
    }
}

Was an diesem Code meiner Meinung nach am unschönsten ist, ist der Einsatz von reduce für foldLeft. Sicher, es funktioniert hier, aber nur weil es sich um einen sequentiellen Stream handelt. Genaugenommen ist das Stream::concat also gelogen, und ein paralleler Aufruf würde bestimmt lustige Ergebnisse liefern. Es scheint aber keine wirklich “saubere” Alternative zu geben, die gleichzeitig praktikabel und funktional ist.

Weniger schön ist auch, dass andauernd Listen kopiert werden müssen, weil es leider immer noch keine “richtigen” immutablen Collections in Java gibt.

Dann hat sich herausgestellt, dass die Benutzung von IntStream ziemlich unbequem ist – hier hatte wohl Performance Vorrang vor Benutzerfreundlichkeit.

Und natürlich merkt man deutlich, wieviel bequemer und übersichtlicher List-Comprehensions in Haskell oder For-Comprehensions in Scala im Vergleich zu handgedrechselten Trainwrecks aus map, flatMap und filter sind.

Trotz aller Kritikpunkte sollte man aber anerkennen, dass es jetzt zumindest möglich ist, solchen funktionalen Code auch in Java zu schreiben, auch wenn er deutlich länger und etwas schlechter zu lesen ist.

Der Aufwand, um zu diesem Ergebnis zu kommen, war recht hoch, und wie schon gesagt, eine kollektive Anstrengung. Ich denke aber, dass das auch eine Gewohnheitsfrage ist, und sehr viel leichter fällt, wenn man sich erst einmal an die Stream-API gewöhnt hat. Außerdem hoffe ich, dass die nächsten Java-Versionen an der Lambda- und vor allem der Stream-Front noch ein wenig nachbessern, um die Programmierung ein wenig intuitiver zu gestalten.

Eine Builder-Variante mit Initialisierungsblöcken

Eines der weniger oft genutzen Java-Features sind Initialisierungsblöcke. Aber wenn sie dann einmal gebraucht werden, können sie ziemlich nützlich sein. Vielleicht auch, um Builder zu schachteln? Und wie könnte das aussehen?

Nehmen wir an, wir müssten eine einfache hierarchische Struktur aufbauen: Eine Menü-Leiste, darin Menüs, und in diesen wiederum Menüpunkte. Mit Initialisierungsblöcken könnte der Aufruf dann so aussehen:

MenuBarBuilder menuBarBuilder = new MenuBarBuilder() {{
    new Menu("menu1") {{
        new Item("item1.1");
        new Item("item1.2");
    }};
    new Menu("menu2") {{
        new Item("item2.1");
        new Item("item2.2");
        new Item("item2.3");
    }};
}};

System.out.println(menuBarBuilder);

Der Einfachheit halber bauen wir hier das Menü nicht zusammen (was ja nicht schwer ist, wenn man die Builder-Struktur erst einmal hat), sondern geben einfach nur eine String-Repräsentation aus. Hier wäre das Ergebnis:

menubar[
  menu 'menu1'[item 'item1.1', item 'item1.2'], 
  menu 'menu2'[item 'item2.1', item 'item2.2', item 'item2.3']]

Okay, bis auf die geschweiften Doppel-Klammern sieht die Verwendungsseite eigentlich gar nicht so schlimm aus. Aber welche Scheußlichkeiten müssen wir bei der Implementierung begehen, damit das funktioniert? Ich finde, auch die Implementierung ist recht erträglich, denn wir benutzen einen einfachen Trick: Menu ist eine innere Klasse von MenuBarBuilder, und kann sich somit bei der Objekterzeugung bei “seiner” äußeren Instanz “registrieren” (in diesem Fall einfach in eine vorgegebene Liste eintragen). Genauso ist Item eine innere Klasse von Menu und registriert sich dort. Dieser Aufbau löst eine Menge Probleme – und es wird nicht einmal ein statischer Import benötigt. Hier ist der Code:

import java.util.ArrayList;
import java.util.List;

public class MenuBarBuilder {

    private List<Menu> menus = new ArrayList<>();

    public String toString() {
        return "menubar" + menus.toString();
    }

    public class Menu {
        private final String menuName;
        private List<Item> items = new ArrayList<>();

        public Menu(String name) {
            this.menuName = name;
            MenuBarBuilder.this.menus.add(this);
        }

        public String toString() {
            return "\n  menu '" + menuName + "'" + items;
        }

        public class Item {
            private final String itemName;

            public Item(String name) {
                this.itemName = name;
                Menu.this.items.add(this);
            }

            public String toString() {
                return "item '" + itemName + "'";
            }

        }
    }

}

Ja, das ist alles was man braucht, damit der Aufruf oben funktioniert. Im Endeffekt ergibt sich die Einfachheit daraus, dass wir die Builder genau so ineinanderschachteln, wie auch unsere Struktur später aussehen soll.

Wie ist diese Builder-Variante nun einzuschätzen? Der größte Nachteil ist wohl, dass sie durch die Verwendung von Initialisierungsblöcken einfach ungewohnt ist. Weiterhin skaliert dieser Ansatz nicht so gut: Bei tiefen Hierarchien wird die Builder-Klasse immer länger, weil immer neue innere Klassen dazukommen, die man nicht auslagern kann. Außerdem wird für jeden verwendeten Initialisierungsblock eine anonyme Klassen erzeugt und eine entsprechende class-Datei angelegt, was bei viel geschachteltem “Inhalt” ebenfalls problematisch sein kann (allerdings würde in diesem Fall auch das “normale” Builder-Pattern unbequem werden). Auf der Haben-Seite dieses Konstrukts steht ein recht einfacher Aufbau und eine hohe Flexibilität.

Ehrlich gesagt bin ich mir nicht sicher, ob die vorgestellte Builder-Variante eine gute Idee ist, aber es war auf jeden Fall spannend, damit herumzuspielen, und überraschend, wie gut sie funktioniert.

Popularität Programmiersprachen – 2014-08

Hallo Leute,

hier sind die Statistiken des Monats August.

Popularität der Programmiersprachen für August 2014:
(Zuwachs oder Schrumpfung in Prozent über die 13 vorherigen Monate)

Popularität Programmiersprachen 2014-08 - Rangfolge

Popularität der Programmiersprachen für August 2014:
(Statistische Zahlen zu den Sprachen)

Popularität Programmiersprachen 2014-08 - Statistische Zahlen

INFO: Die Statistiken für den September und Oktober erscheinen Anfang November.

Viel Spasz weiterhin
Falk

Popularität Programmiersprachen – 2014-07

Hallo Leute,

hier sind die Statistiken des dritten Monats.

Popularität der Programmiersprachen für Juli 2014:
(Zuwachs oder Schrumpfung in Prozent über die 13 vorherigen Monate)

Popularität Programmiersprachen 2014-07 - Rangfolge

Popularität der Programmiersprachen für Juli 2014:
(Statistische Zahlen zu den Sprachen)

Popularität Programmiersprachen 2014-07 - Statistische Zahlen

Viel Spasz weiterhin
Falk

Popularität Programmiersprachen – 2014-06

Hallo Leute,

hier sind die Statistiken des zweiten Monats. Neben der Veränderung in Prozent und den statistischen Zahlen gibt es nun noch die Veränderung des Ranges. Eine Null steht dabei für keine Veränderung im Rang, positive Zahlen für ein Aufsteigen und negative Zahlen für ein Absteigen im Rang.

Popularität der Programmiersprachen für Juni 2014:
(Zuwachs oder Schrumpfung in Prozent über die 13 vorherigen Monate)

Popularität Programmiersprachen 2014-06 - Rangfolge

Popularität der Programmiersprachen für Juni 2014:
(Statistische Zahlen zu den Sprachen)

Popularität Programmiersprachen 2014-06 - Statistische Zahlen

Viel Spasz weiterhin
Falk

Popularität Programmiersprachen – 2014-05

Hallo Leute,

da dies meine erste Veröffentlichung auf diesem Blog ist, möchte ich mich kurz vorstellen. Mein Name Falk, ich bin 38 Jahre alt, komme aus Dresden und bin gerade mit meinem zweiten Studium der Informationstechnik fertig geworden. Ab jetzt bin ich Java-Entwickler in Dresden. Ich kenne Daniel seit zwei Jahren – der Grund hierfür war Scala.

Da ich mich seit längerem mit der Popularität von Programmiersprachen beschäftige und dazu eine Statistik führe, gab es von uns die Idee, diese Statistik hier auf diesem Blog zu veröffentlichen.

Die Quelle zu dieser Statistik ist lediglich die Seite stackoverflow.com, deren Genehmigung für die Erhebung und Veröffentlichung ich mir bereits eingeholt habe. Täglich um 14:00 mitteleuropäischer Zeit wird die Anzahl an Fragen von insgesamt 54 Programmiersprachen erfasst. Mit diesen Zahlen wird dann eine Statistik erstellt. Aus den Zahlen zum monatsletzten Tag, werden die durchschnittlichen Fragen pro Sprache und Tag für den Monat errechnet. Anschlieszend wird für jede Sprache der Anteil der Fragen von allen 54 Sprachen für einen Monat berechnet. Aus diesen monatlichen Prozentangaben, über den Zeitraum von 13 Monaten, werden dann die Parameter der Regressionsgerade bestimmt. Aus der Höhendifferenz der Regressionsgerade erhält man den Zuwachs oder die Schrumpfung des Prozentanteils von allen Sprachen. In Abhängigkeit von diesem Anteil, werden Sprachen mit wenigen Fragen von der weiteren Betrachtung ausgeschlossen. Aus diesem Grund ist die Summe der Prozentanteile nicht genau Null. Die übriggebliebenen Sprachen werden nach ihrem Anteil sortiert und in einem Diagramm dargestellt. Solch ein Diagramm soll monatlich erscheinen. Zusätzlich zu diesem Diagramm soll die durchschnittliche Fragenanzahl der letzten 13 Monate pro Tag angegeben werden und für jede übriggebliebene Sprache, die durchschnittliche Fragenanzahl pro Tag (über die letzten 13 Monate), die Differenz an Fragen über diese Zeit und der prozentuale Anteil dieser Differenz gegenüber der Fragenanzahl der letzten 13 Monate.

 

Popularität der Programmiersprachen für Mai 2014:
(Zuwachs oder Schrumpfung in Prozent über die 13 vorherigen Monate)

Popularität Programmiersprachen 2014-05 - Rangfolge

Popularität der Programmiersprachen für Mai 2014:
(Statistische Zahlen zu den Sprachen)

Popularität Programmiersprachen 2014-05 - Statistische Zahlen

Für diesen Monat und für jeden Dezember, wird der Verlauf der Fragenanzahl der 54 Fragen auf stackoverflow.com in einem Diagramm dargestellt:

Popularität Programmiersprachen 2014-05 - Fragen auf stackoverflow.com (54 Sprachen)

Viel Spasz weiterhin
Falk

Casts mit zusätzlichen Bounds in Java 8

Immer wieder beschert mir Java wundervolle WTF-Momente, so auch heute. Eine Syntaxerweiterung in Java 8, die komplett an mir vorbeigegangen ist, sind Casts mit zusätzlichen Bounds:

LinkedList<String> list = new LinkedList<>();
List<String> list1 = (List & Queue) list; //OK
List<String> list2 = (List & RandomAccess) list; //ClassCastException

Die JLS schreibt dazu recht lakonisch :

If the cast operator contains a list of types – that is, a ReferenceType followed by one or more AdditionalBound terms – then all of the following must be true, or a compile-time error occurs.

Stellt sich die Frage ist, wozu das Ganze gut sein soll. Der einzige sinnvolle Anwendungsfall, den ich gefunden habe, ist die Spezifizierung zusätzlicher Interfaces bei Lambdas:

Runnable r = (Runnable & Serializable) () -> System.out.println("Serializable!");

Lambdas haben ja eigentlich keinen Typ, sie sind ein wenig wie Schrödingers Katze: Erst wenn man etwas mit ihnen anstellt – etwa die Zuweisung zu einer Variablen – entscheidet sich, was ihr Typ ist. Durch den vorgelagerten Cast wird diese Typbestimmung vorgezogen, so dass das Objekt r nicht nur Runnable, sondern auch Serializable ist, und später auch problemlos serialisiert werden kann.

Wie findet ihr dieses etwas obskure Feature? Seht ihr noch andere sinnvolle Anwendungsmöglichkeiten?

Pattern-Matching in Java 8

In Scala ist Pattern-Matching ein beliebtes Feature mit fast unbegrenzten Anwendungsmöglichkeiten. Nicht umsonst wird es oft als “switch auf Steroiden” bezeichnet. Im Rahmen eines kleinen Projekts habe ich überlegt, ob man das nicht auch in Java 8 nachbauen kann.

Man kann, mit einigen Einschränkungen. Hier einmal ein kleines Anwendungsbeispiel:

String string = ...
String s = match(string,
   StartsWith("foo", () -> "found foo..."),
   EndsWith("bar", () -> "found ...bar"),
   Contains("baz", () -> "found ...baz..."),
   EqualsIgnoreCase("blubb", () -> "found blubb"),
   Default(() -> "nix gefunden")
);
System.out.println(s);

Wie funktioniert das? Zuerst braucht man für die einzelnen Fälle ein Interface:

import java.util.Optional;

public interface Case<T,R> {
    Optional<R> accept(T value);
}

Es ist im Prinzip eine Art Funktion, die für einem Ausgangswert ein Resultat liefert, allerdings in einem Optional. Wir haben also eine Art Funktion, die eventuell etwas zurückliefert – genau dann, wenn der zu überprüfende Wert “passt”. Hier eine der verwendeten Case-Implementierungen (man verzeihe mir bitte die an Scala angelehnte Großschreibung der Methodennamen):

public static <R> Case<String, R> StartsWith(String prefix, Supplier<R> supplier) {
    return t -> t.startsWith(prefix)
            ? Optional.of(supplier.get())
            : Optional.<R>empty();
}

Wir liefern also den durch den Supplier vorgegebenen Wert (in ein Optional verpackt) zurück, aber nur, wenn der zu testende String den richtigen Präfix hat.

Die match-Methode ist auch recht unspektakulär (MatchException ist eine gewöhnliche Laufzeit-Exception):

@SafeVarargs
public static <T,R> R match(T value, Case<T,R> ... cases) throws MatchException {
    for(Case<T,R> c : cases) {
        Optional<R> result = c.accept(value);
        if (result.isPresent()) {
            return result.get();
        }
    }
    throw new MatchException();
}

Interessanterweise kann dieser Mechanismus auch die Arbeit mit Optional vereinfachen. Wenn wir einmal so tun, als hätte Optional wie in Scala die Unterklassen Some (die einen Wert enthält) und None (die “leer” ist), könnte uns das zu dieser Verwendung anregen:

Optional<Integer> opt = ...
String s = match(opt,
        None(() -> "leer"),
        Some(42, () -> "die Antwort!"),
        SomeIf(a -> "gerade", a -> a % 2 == 0),
        Default(() -> "nix gefunden")
);

Selbst “geschachteltes” Pattern-Matching ist möglich:

Optional<Optional<Integer>> opt = ...
String s = match(opt,
        None(() -> "leer"),
        Some(None(() -> "leer eingetütet")),
        Some(SomeIf(a -> "gerade", a -> a % 2 == 0)),
        Default(() -> "nix gefunden")
);

Hier die verwendeten Case-Implementierungen:

import static java.util.Optional.*;
...

public static <T,R> Case<Optional<T>,R> None(Supplier<? extends R> supplier) {
    return t -> t.isPresent()
            ? Optional.<R>empty()
            : of(supplier.get());
}

public static <T,R> Case<Optional<T>,R> Some(T value, Supplier<? extends R> supplier) {
    return t -> t.isPresent() && t.get().equals(value)
            ? of(supplier.get())
             : Optional.<R>empty();
}

public static <T,R> Case<Optional<T>,R> Some(Case<T,R> caseT) {
    return t -> t.isPresent()
            ? caseT.accept(t.get())
            : Optional.<R>empty();
}

public static <T,R> Case<Optional<T>,R> Some(Function<? super T, ? extends R> fn) {
    return t -> t.isPresent()
            ? of(fn.apply(t.get()))
            : Optional.<R>empty();
}

public static <T,R> Case<Optional<T>,R> SomeIf(Function<? super T, ? extends R> fn, Predicate<T> predicate) {
    return t -> t.isPresent() && predicate.test(t.get())
            ? of(fn.apply(t.get()))
            : Optional.<R>empty();
}

public static <T,R> Case<T,R> Default(Supplier<R> supplier) {
    return value -> of(supplier.get());
}

Ich will nicht verhehlen, dass wir hier haarscharf an den Grenzen von Javas Typ-Inferenz operieren, und auch leicht Eindeutigkeitsprobleme bei gleichnamigen statischen Methoden mit Lambda-Argumenten auftreten können. Ob diese kleine DSL wirklich “brauchbar” ist, muss sich erst noch zeigen. Spaß macht es auf jeden Fall.

Typsicheres Builder-Pattern mit Java

Der folgende Code ist eine ziemlich direkte Übersetzung des typsicheren Builder-Patterns aus Scala nach Java. Es gibt mehrere Versionen, aber mir gefällt die Variante von Daniel Sobral am besten, weil damit auch bei der “Verkabelung” im Builder weniger Fehler möglich sind. Etwas kürzer ist die ursprüngliche Version von Rafael Ferreira, aber dort ist die Zuordnung von Typparametern zu den Werten “willkürlich”, so dass man beim Builder-Schreiben eher etwas falsch machen kann.

Zuerst zu unserer zu bauenden Klasse und einem kleinen Anwendungsbeispiel:

public class Person {

  private String firstName;
  private String lastName;
  private int age;

  public Person(String firstName, String lastName, int age) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }

  public String getFirstName() { return firstName; }

  public String getLastName() { return lastName; }

  public int getAge() { return age; }

  @Override
  public String toString() {
    return "Person{" +
      "firstName='" + firstName + '\'' +
      ", lastName='" + lastName + '\'' +
      ", age=" + age +'}';
  }
}

...

Person person = build(NEW.withLastName("Mustermann").withAge(62).withFirstName("Heinz"));

//das compiliert *nicht*
Person person = build(NEW.withLastName("Mustermann").withAge(62));

Zentral für Daniel Sobrals Idee ist ein Wrapper-Typ mit zwei Ausprägungen (“da” und “nicht da”) für die zu sammelnden Werte. Leider hat Optional in Java 8 keine Unterklassen wie Option in Scala, deshalb brauchen wir unsere eigene kleine Hierarchie. Und dann können wir auch gleich andere Namen verwenden, die unserem Zweck eher entsprechen:

public interface Value<T> {}

public final class Without<T> implements Value<T> {
  private final static Without<?> WITHOUT = new Without<>();

  private Without(){}

  @SuppressWarnings("unchecked") 
  public static <T> Without<T> without() { 
    return (Without<T>) WITHOUT; 
  }
}

public final class With<T> implements Value<T> {
  private final T t;

  public With(T t) { this.t = t; }

  public T get() { return t; }
}

Man könnte die Without-Klasse auch hübscher (also ohne Cast) schreiben, aber mit meiner Variante kommt man mit einem einzigen Objekt aus. Nun zur eigentlichen Magie, dem Builder. Ich bitte zu beachten, dass die Klasse mit ihrem Generic-Verhau zwar ziemlich länglich aussieht, der Nutzer davon aber nichts mitbekommt (siehe Anwendungsbeispiel oben).


import static builder.Without.without;

public class PersonBuilder<FirstName extends Value<String>,
          LastName extends Value<String>,
          Age extends Value<Integer>> {

  public static final PersonBuilder<Without<String>, Without<String>, Without<Integer>> NEW =
    new PersonBuilder<Without<String>, Without<String>, Without<Integer>>(without(), without(), without());

  private FirstName firstName;
  private LastName lastName;
  private Age age;

  private PersonBuilder(FirstName firstName, LastName lastName, Age age) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }

  public PersonBuilder<With<String>, LastName, Age> withFirstName(String firstName) {
    return new PersonBuilder<>(new With<>(firstName), lastName, age);
  }

  public PersonBuilder<FirstName, With<String>, Age> withLastName(String lastName) {
    return new PersonBuilder<>(firstName, new With<>(lastName), age);
  }

  public PersonBuilder<FirstName, LastName, With<Integer>> withAge(int age) {
    return new PersonBuilder<>(firstName, lastName, new With<>(Integer.valueOf(age)));
  }

  public static Person build(PersonBuilder<With<String>, With<String>, With<Integer>> builder) {
    return new Person(builder.firstName.get(), builder.lastName.get(), builder.age.get());
  }
}

Das Prinzip ist trotz der Generic-Orgie recht einfach zu verstehen: Ausgehend von einem Builder mit lauter Withouts werden letztere schrittweise durch With-Typparameter mit den zugehörigen Werten ersetzt. Und nur ein Builder mit lauter With-Typparametern kann verwendet werden, um am Ende eine Person zu erzeugen (deshalb muss das auch eine statische Methode statt wie üblich eine Instanzmethode sein). Man sieht in der build-Methode, wie die Generics (im Gegensatz zu Rafael Ferreiras Version) wirklich mit den internen Werten gekoppelt sind: Nur mit With als Typparametern sind die get-Methoden verfügbar.

Natürlich erscheint der vorgestellte Code erst einmal ziemlich umfangreich. Dabei sollte man aber berücksichtigen, dass der Unterschied zu einem “normalen” Builder ohne Compilezeit-Test von erforderlichen Argumenten nicht besonders groß ist: Da die Value-Hierarchie wiederverwendet werden kann, kommen im Prinzip nur die Generics im Builder hinzu. Ich denke, wenn man sich sowieso schon dazu entschieden hat, einen Builder zu verwenden, ist es naheliegend, gleich diese Version zu verwenden, wenn es in der zu erzeugenden Klasse “Pflichtfelder” gibt.

zipWith in Java 8

Heute einmal ein ziemlich einfaches Beispiel, wie Lambdas das Leben in Java 8 leichter machen. In Haskell und Scala gibt es die Funktione zipWith, mit der zwei Datenstrukturen wie Listen durch elementweise Verknüfung zu einer neuen “zusammengeklebt” werden. Dabei muss man aufpassen: Ist eine der Ausgangsstrukturen länger als die andere, werden die “überflüssigen” Elemente einfach ignoriert. In Java bietet sich so eine Funktion an mindestens zwei Stellen an: Bei Iterables und bei den neuen Streams. Da ich mich mit letzteren (noch) nicht so gut auskenne, will ich heute den einfacheren ersten Fall behandeln.

Ein besonders nützlicher Anwendungsfall für zipWith ist, wenn man mit der erweiterten for-Schleife zwei Collections gleichzeitig durchgehen will – vorher musste man meist auf andere Sprachmittel (z.B. Indexe oder Iteratoren) ausweichen. Wie könnte nun so eine Schleife aussehen?

List<String> strings = Arrays.asList("a","b","c");
List<Integer> ints = Arrays.asList(6,9,14,32);
for(String result : zipWith(strings, ints, (s,i) -> s + i)) {
   System.out.println(result);
}

Das erwartete Ergebnis wären hier die Zeilen “a6″, “b9″ und “c14″. Natürlich wäre auch eine anonyme Klasse an Stelle des Lambda-Ausdrucks möglich gewesen, aber erst durch diesen wird das ganze Konstrukt lesbar. Die Umsetzung ist trivial:

import java.util.function.BiFunction;
...
public static <A,B,C> Iterable<C> zipWith(Iterable<A> iterableA, Iterable<B> iterableB, BiFunction<A,B,C> fn) {
    return () -> new Iterator<C>() {
        private Iterator<A> itA = iterableA.iterator();
        private Iterator<B> itB = iterableB.iterator();

        public boolean hasNext() {
            return itA.hasNext() && itB.hasNext();
        }

        public C next() {
            return fn.apply(itA.next(), itB.next());
        }
    };
}

Wer sich wundert, wo das “new Iterable” geblieben ist: Da das Interface nur eine Methode (nämlich iterator()) besitzt, können wir es durch einen Lambda-Ausdruck ersetzen. Wir brauchen auch die remove-Methode von Iterator nicht zu implementieren, es gibt in Java 8 eine Default-Methode dafür (die eine UnsupportedOperationException wirft). Und als letztes fällt auf, dass wir aus dem Lambda-Ausdruck heraus auf iterableA und iterableB zugreifen konnten, ohne dass wir diese final machen mussten. Da beide Argumente nicht (weder in der Methode noch im Lambda-Ausdruck) verändert werden, sind sie “effektiv final” und benötigen keinen entsprechenden Modifikator.

Das war jetzt etwas leichtere Kost, aber ich hoffe trotzdem ein wenig nützlich.

Wer weiß, wie man das Gleiche mit Streams anstellt, darf seine Lösung hier gerne vorstellen, ich bin gespannt darauf…

[Update]

Für Streams habe ich auf Stackoverflow diesen Schnipsel gefunden:

public static<A, B, C> Stream<C> zip(Stream<? extends A> a,
                                     Stream<? extends B> b,
                                     BiFunction<? super A, ? super B, ? extends C> zipper) {
    Objects.requireNonNull(zipper);
    @SuppressWarnings("unchecked")
    Spliterator<A> aSpliterator = (Spliterator<A>) Objects.requireNonNull(a).spliterator();
    @SuppressWarnings("unchecked")
    Spliterator<B> bSpliterator = (Spliterator<B>) Objects.requireNonNull(b).spliterator();

    // Zipping looses DISTINCT and SORTED characteristics
    int both = aSpliterator.characteristics() & bSpliterator.characteristics() &
            ~(Spliterator.DISTINCT | Spliterator.SORTED);
    int characteristics = both;

    long zipSize = ((characteristics & Spliterator.SIZED) != 0)
            ? Math.min(aSpliterator.getExactSizeIfKnown(), bSpliterator.getExactSizeIfKnown())
            : -1;

    Iterator<A> aIterator = Spliterators.iterator(aSpliterator);
    Iterator<B> bIterator = Spliterators.iterator(bSpliterator);
    Iterator<C> cIterator = new Iterator<C>() {
        @Override
        public boolean hasNext() {
            return aIterator.hasNext() && bIterator.hasNext();
        }

        @Override
        public C next() {
            return zipper.apply(aIterator.next(), bIterator.next());
        }
    };

    Spliterator<C> split = Spliterators.spliterator(cIterator, zipSize, characteristics);
    return (a.isParallel() || b.isParallel())
           ? StreamSupport.stream(split, true)
           : StreamSupport.stream(split, false);
}

Der Original-Code war wohl in der Stream-Implementierung im Lambda-Projekt dabei und ist später unverständlicherweise rausgeflogen.