Java 8 Lambda Tutorial: Einstieg in Lambda und Streams

Mit Java 8 wurden so genannte Lambdas (oder Closures) eingeführt. Das Iterable Interface und das Collection Interface haben neue Methoden bekommen. Die API erlaubt einen funktionalen Programmierstil – was vorher noch nicht möglich war. Mit diesem Java 8 Lambda Tutorial möchte ich euch eine kleine Einführung mit vielen praktischen Beispielen geben, damit ihr einen Ansatz habt, wie das Ganze funktioniert.

Vor Java 8 sieht eine Methode um alle Personen mit einem bestimmten Namen zu extrahieren meist so aus…

public List<Person> getPersonsWithName(List<Person> searchList,String firstName)
{
 List<Person> foundPersons = new ArrayList<>();
 
 for(Person p : searchList)
 {
     if(p.getFirstName().equals(firstName))
       foundPersons.add(p);
 }
 
 return foundPersons;
}

Durch die neuen Methoden in allen Collections, können wir folgendes machen…

public List<Person> getPersonsWithName(List<Person> searchList,String firstName)
{
  return searchList.stream().filter(p -> p.getFirstName().equals(firstName)).collect(Collectors.toList());
}

Syntax

Grundsätzlich ist die API mit der Syntax ((Platzhalter) -> Bedingung/Aktion) aufgebaut

List<String> lst = new ArrayList<>();
lst.forEach(str -> System.out.println(str));

Eine Abkürzung für solche Ausdrücke bietet der :: Operator. Die Beispiele weiter unten im Beitrag werden das noch verdeutlichen.

List<String> lst = new ArrayList<>();
lst.forEach(System.out::println);

Alle Beispiele basieren auf folgender Person-Klasse

public class Person {

  private long id;
  private String firstName;
  private String secondName;
  private Gender gender;
  private LocalDate brithdate;

  private List<Person> friends;

  // Getter,Setter,Equals,HashCode,ToString
}
public enum Gender {
    MAN,WOMAN
}

Die persons-Variable ist eine Liste von Personen (wird beim Start des Programmes initialisert).


ForEach

  • foreach (aus dem Iterable-Interface) – Für jedes Element in der Liste
// Gibt alle Personen in der Konsole aus
persons.forEach(System.out::println);

// Gibt alle Geburtstage in der Konsole aus
persons.forEach(p -> System.out.println(p.getBrithdate()));

// Personen, gruppiert anhand des Vornamens ausgeben
Map<String, List<Person>> personsGroupedByFirstName = getPersonsGroupedByFirstName();
personsGroupedByFirstName.forEach((name, persons) -> System.out.printf("Name: %s, Vorkomnisse: %d%n", name, persons.size()));

Stream/ParallelStream

  • stream()/parallelStream() – Öffnet einen Stream, der dann weiter verarbeitet werden kann – Generell ist es so, dass wir zunächst einen Stream öffnen, um ihn dann zu reduzieren bzw. weiter zu verarbeiten

Die filter() Methode, filtert alle Ergebnisse, die auf die angegebene Bedingung zutreffen. Diese Methode gibt ebenfalls einen Stream zurück, der dann weiter verarbeitet werden kann

// Filtert die Liste anhand des Vornamens
persons.stream().filter(p -> p.getFirstName().equals(firstName))

Die min()/max() Methode findet das minimale/maximale Element was auf den angegeben Comparator zutrifft. In dem Codebeispiel ist ebenfalls zu sehen, wie die Syntax für einen Comparator mit den neuen Lambda Expressions aussieht.

// Älterste Person
persons.stream().min((p1, p2) -> p1.getBrithdate().compareTo(p2.getBrithdate()))

Über die collect()-Methode lassen sich Ergebnisse gruppieren oder zusammenfassen…

// Personen mit bestimmten Vornamen, Rückgabe als List<Person>
persons.stream().filter(p -> p.getFirstName().equals(firstName)).collect(Collectors.toList());

// Personen anhand von dem Vornamen gruppieren, gibt eine Map<String,List<Person>> wieder
persons.stream().collect(Collectors.groupingBy(Person::getFirstName));

// Personen anhand von Geburtsdatum gruppieren und die Frequenz ermitteln, gibt eine Map<LocalDate,Long> wieder
persons.stream().collect(Collectors.groupingBy(Person::getBrithdate, Collectors.counting()));

Die map()-Methode wechselt die Ebene des Streams auf die angegebene Eigenschaft. Wie man hier sieht, lassen sich die ganzen Methoden verschachteln. getFirstName() ist ein String und durch die map()-Methode bekommen wir also ein Stream von Strings. Den könnten wir theoretisch weiter einschränken mit filter().

// Liste von Vornamen als List<String> 
persons.stream().map(Person::getFirstName).collect(Collectors.toList());

Die distinct()-Methode kennen sicherlich einige aus der Datenbankwelt – diese sorgt dafür, dass kein Ergebnis doppelt ist. Wenn wir die vorherige Methode erweitern wollen, so dass die Vornamen nur einmalig sind, sieht das folgendermaßen aus..

// Liste von einmaligen Vornamen als List<String> 
persons.stream().map(Person::getFirstName).distinct().collect(Collectors.toList());

limit() beschränkt die Anzahl der Ergebnisse auf die angegebene Anzahl

// 5 Personen
persons.stream().limit(5);

Die removeIf()-Methode entfernt alle Elemente aus der Liste, die auf die Bedingung zutreffen.

// Alle Frauen aus der Liste entfernen
persons.removeIf(p -> p.getGender().equals(Gender.WOMAN));

anyMatch() gibt einen boolean zurück und prüft, ob irgendein Element in der Collection auf die Bedingung zutrifft.

// Hat irgendjemand an dem Tag date Geburtstag
persons.stream().anyMatch(p -> p.getBrithdate().equals(date));

allMatch() gibt einen boolean zurück und prüft, ob ALLE Elemente in der Collection auf die Bedingung zutreffen. Alternativ gibt es auch noch die noneMatch()-Methode.

// Prüft, ob alle Personen männlich sind
persons.stream().allMatch(p -> p.getGender().equals(Gender.MAN));

sorted() sortiert den Stream – optional kann ein eigener Comparator übergeben werden.

// Standardsortierung
persons.stream().sorted()...

// Eigene Sortierung, nach Nachnamen
persons.stream().sorted((p1,p2) -> p1.getSecondName().compareTo(p2.getSecondName()))...

findFirst() findet das erste Element, was es im Stream() findet. Der Rückgabewert ist Optional<T>, da potentiell kein Element gefunden wird. Über orElse() können wir sicherstellen, dass ein Objekt<T> zurückgegeben wird.

// Findet die erste Person, anhand der Id
persons.stream().filter(p -> p.getId() == id).findFirst().orElse(null);

Über Arrays.asStream() oder Arrays.asList().stream() lassen sich Arrays streamen.

// Summe des Arrays
Arrays.stream(new int[] {1,10,50,5,18}).sum();

Wie bereits erwähnt, geben die meisten Methoden (distinct(), map(),min(),max(), sorted()…) einen Stream zurück, auf die die Funktionen dann wieder anwendbar sind – somit ist eine Schachtelung möglich.

persons.stream().filter(p -> p.getFirstName().equals("Kevin")).distinct().sorted((p1, p2) -> p1.getSecondName().compareTo(p2.getSecondName())).collect(Collectors.toList());

Comparators/Komparatoren

Über die neuen Lambda-Expression können wir ebenfalls einen Comparator definieren. Die Syntax dafür lautet ((Objekt1, Objekt2) -> Vergleich).

// Personen anhand von Vornamen sortieren
persons.sort((p1, p2) -> p1.getFirstName().compareTo(p2.getFirstName()));

// Personen anhand von Geburtstag sortieren
persons.sort((p1, p2) -> p1.getBrithdate().compareTo(p2.getBrithdate()));

Lambda Ausdrücke in Eclipse

Falls Eclipse rummeckert, wenn ihr Lambda-Ausdrücke schreibt, ist der Compiler wahrscheinlich noch auf < 1.8 eingestellt. In den Einstellungen unter Window -> Preferences muss das Compiler compliance level auf 1.8 gestellt werden.

Eclipse Compiler auswählen
Eclipse Compiler auswählen

Das komplette Projekt mit allen hier aufgelisteten Lambda Ausdrücken und weiteren Ausdrücken, samt Dokumentation, könnt ihr euch hier herunterladen.

Java 8 Lambdas und Streams

 

Comments

comments

Leave a Reply