Java Getriebe

Java und NetBeans

Streams und Maps

Es gibt gefühlt hunderte Seiten, die sich mit der Streams API aus Java 8 befassen. Alle haben furchtbar tolle Tipps und vor allem Beispiele, wie man die Daten aus seinen Listen über Streams und den unausweichlichen Lambdas verarbeiten, filter und umwandeln kann. In unserer (gewachsenen) Anwendung verwenden wir allerdings weniger oft Listen und Sets als viel mehr etliches an Maps. Der Großteil unserer Daten sind als Schlüssel-Wert Paare gespeichert. Wenig ist eine „beliebige“ Datenmenge sondern explizit benannte Daten.

Die Stream sind allerdings auf einzeln aufgereihte Objekte ausgelegt und nicht auf Name-Value-Datenpaare. Dies macht das Handling innerhalb der Streams etwas unhandlich, vor allem, wenn man zwar durch die Values iterieren möchte, diese auch gerne per map() in andere Datentypen umwandeln möchte, aber der Schlüssel darf dabei nicht verloren gehen, da dieser unbedingt mit den zugehörigen Daten bzw. Objekten verknüpft bleiben sollte oder gar muss.

Für die Verarbeitung dieser Paare innerhalb der Streams bedeutet dies meist, dass man irgendwie mit Map.Entry<> 1 hantiert. Diese sollte man in der Regel allerdings als Immutable betrachten, so dass man bei jeder Änderung von Key oder (häufiger) Value eine neue Instanz erstellen muss. Leserlicher wird der Code dadurch selten.

Mein Ansatz für einen etwas leserlicheren Code ist der folgende:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static <K, V> Map.Entry<K, V> toEntry(K key, V value) {
  return new AbstractMap.SimpleImmutableEntry<>(key, value);
}

public static <K, V, M> Map.Entry<M, V> mapKey(
        Map.Entry<K, V> org, Function<K, M> mapper) {
  return toEntry(mapper.apply(org.getKey()), org.getValue());
}

public static <K, V, M> Map.Entry<K, M> mapValue(
        Map.Entry<K, V> org, Function<V, M> mapper) {
  return toEntry(org.getKey(), mapper.apply(org.getValue()));
}

Damit lässt sich der Quelltext mit den Streams deutlich kürzer schreiben und enthält keine new Aufrufe die in diesem Zusammenhang nur den Lesefluss stören.

Ein Beispiel

In „parameters“ ist eine Menge von „Parameter“ Objekten die jeweils ein „Name“-„Wert“ Paar darstellen. Für eine Fremdbibliothek müssen unsere Namen in „deren“ Namen umgewandelt werden und dann diese neuen Schlüssel mit allen nicht-null Werten von „Parameter“ in eine bestehende Map „data“ (die bereits andere Daten enthält) geschrieben werden.

1
2
3
4
5
6
7
8
9
10
11
parameters.stream()
    // get all Mapping (m) to all Parameter (p)
    .map(p -> toEntry(p, mappingManager.get(p)))
    .filter(pm -> pm.getValue() != null)
    .map(pm -> mapValue(vm, m -> m.getKey()))
    .map(pk -> mapKey(pk, p -> p.getValue()))
    .filter(vk -> vk.getKey() != null)
    // write all Values (v) with the mapped keys (k) to data
    .collect(() -> data,
        (map, vk) -> map.put(vk.getValue(), vk.getKey()),
        Map::putAll);

ps: Ich will nicht behaupten, dass das Beispiel guter Code ist. Er soll nur zeigen, dass die Hilfsmethoden die Übersichtlichkeit beim Arbeiten mit Key-Value-Paaren erhöht.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.