Streams und Null Werte

In dieser Folge der Java 9 Perlen möchten wir uns das Handling von Null Werten in Streams etwas genauer anschauen.


Wir starten mit einer SkillsService Klasse, die ein Mapping von Mitarbeiter auf Skills macht. Es ist eine Implementierung, die auf Arrays basiert.

public class SkillsService {
  private static Map<String, String[]> namesAndSkills = Map.ofEntries( 
    Map.entry("Franz",     new String[] { "C", "Java" }), 
    Map.entry("Alex",      new String[] { "PHP" }), 
    Map.entry("Emil",      new String[] { "JavaScript, TypeScript" }), 
    Map.entry("Christian", new String[] { "Python" }), 
    // ....
    Map.entry("Andrea",    new String[] { "Ruby" }));

    public static String[] getSkills(String name) {
      return namesAndSkills.get(name);
    }
}

Für Franz erhalte ich die Skills C und Java und für Hugo (der erst nächsten Monat in der Firma beginnen wird) null.

Wir möchten nun die Skills von einzelnen Mitarbeitern auswerten und dazu den SkillsService zusammen mit Streams verwenden. Dies geht einfach und elegant mit der of() Methode der Stream Klasse (seit Java 8).

API

public static<T> Stream<T> of(T… values)

Returns a sequential ordered stream whose elements are the specified values.

Die of() Methode wandelt die gefundenen Skill in einen Stream<String> und mittels count() bekommen wir die Information, dass Franz 2 Skills hat.

Stream skillsOfFranz = Stream.of(SkillsService.getSkills(“Franz"));
System.out.println("Anzahl Skills von Franz: " + skillsOfFranz.count());

Wenn wir den gleichen Aufruf für Hugo machen, der keine registrierten Skills hat, erhalten wir statt 0 eine java.lang.NullPointerException. Java 9 bietet eine neue Methode ofNullable() um solche NullPointerExceptions zu verhindern.

API

public static<T> Stream<T> ofNullable(T t)

Returns a sequential Stream containing a single element, if t is non-null, otherwise  returns an empty Stream.

Mit ofNullable() erhalten wir als Output einen leeren Stream (falls der Input leer war) oder einen Stream mit einem einzigen Element. Wir können diese Methode gerade benutzen, um das Resultat des SkillServices in einen Stream zu wrappen. Es gilt aber zu beachten, dass wir nicht mehr einen Stream<String> erhalten, sondern einen Stream<String[]>. Weniger schön, aber mittels Stream Operationen kommen wir dennoch zur gewünschten Information:

  • mit findFirst() das einzige Element aus dem Stream holen
  • da findFirst() ein Optional zurückgibt, den Array auf seine Länge mappen bzw. wenn der Stream leer war (d.h. wenn der Input für ofNullable() null war) 0 zurückgeben
Stream<String[]> skillsOfPeter =        
                 Stream.ofNullable(SkillsService.getSkills(“Peter"));

int skillsCountPeter = skillsOfPeter.findFirst().map(s -> s.length).orElse(0);
System.out.println("Anzahl Skills von Peter: " + skillsCountPeter);

Wie gewünscht erhalten wir 0 für die Anzahl Skills von “Peter”.

Es gibt einen zweiten schönen Use Case für ofNullable(), wenn wir mit Listen arbeiten, die auch Null Werte enthalten können. Fangen wir wieder mit einem Beispiel an. Wir haben einen Office Lookup Service, der ein Mapping von Mitarbeiter zu Büro macht.

public class OfficeService {

  private static Map<String, String> namesAndOffices = Map.ofEntries( 
    Map.entry("Franz", "E.12"), 
    Map.entry("Alex", "E.13"), 
    Map.entry("Emil", "E.23"), 
    Map.entry("Christian", "E.10"), 
    // ....
    Map.entry("Andrea", "E.15"));

  public static String getOffice(String name) {
    return namesAndOffices.get(name);
  }

  public static List getOffices(String... names) {
    List offices = new ArrayList<>();
    for (String name : names) {
      offices.add(getOffice(name));
    }
    return offices; 
  }
}

getOffice() liefert zu einem Mitarbeiter sein Büro. Falls der Mitarbeiter noch nicht angefangen hat, hat er folglich noch kein Büro und die Method gibt null zurück. Zusätzlich gibt es noch die Methode getOffices(), die für mehrere Mitarbeiter die Büros in eine Liste aggregiert und zurück gibt. Diese Liste kann potentiell Null Werte enthalten.

Wir hätten nun gerne die Büros (sortiert) von folgenden Personen ausgegeben, wobei Hugo und Otto noch kein Büro haben.

List officesWithNulls = 
     OfficeService.getOffices("Franz", "Hugo", "Emil", "Otto");

Wir wollen uns mehrere Lösungen anschauen und beginnen mit einer einfachen und einleuchtenden Lösung: im Stream manuell die Null Werte filtern und damit die NullPointerExceptions verhindern:

List officesWithFilter = officesWithNulls 
    .stream() 
    .filter(Objects::nonNull) 
    .sorted() 
    .collect(Collectors.toList());

System.out.println(officesWithFilter);

Funktioniert wunderbar. Aber es gibt weitere Möglichkeiten, z.B. mittels flatMap() die einzelnen Werte in Streams packen:

  • Null Werte in einen leeren Stream
  • alle anderen Werte in einen Stream mit nur einem Element

 

List officesWithFlatMap = officesWithNulls 
    .stream() 
    .flatMap(office -> office == null ? Stream.empty() : Stream.of(office)) 
    .sorted() 
    .collect(Collectors.toList());

System.out.println(officesWithFlatMap);

Diese Lösung sollte uns doch irgendwie an ofNullable() erinnern, denn dies Methode macht ja genau das. Daher können wir die zweite Lösung in Java 9 mittel ofNullable() noch eleganter implementieren:

List officesWithFlatMap = officesWithNulls 
    .stream() 
    .flatMap(office -> office == null ? Stream.empty() : Stream.of(office)) 
    .sorted() 
    .collect(Collectors.toList());

System.out.println(officesWithFlatMap);

Zusammenfassend kann man sagen: ofNullable() ist eine kleine sinnvolle Erweiterung des Stream APIs. Ein Funktion, die man nicht täglich braucht, die aber dennoch nützlich sein kann.

Kommentare sind geschlossen.