Skip to content

Technik Blog

Programmieren | Arduino | ESP32 | MicroPython | Python | Raspberry Pi | Raspberry Pi Pico

Menu
  • Smarthome
  • Gartenautomation
  • Arduino
  • ESP32 & Co.
  • Raspberry Pi & Pico
  • Solo Mining
  • Deutsch
  • English
Menu

Java : Lambda Ausdrücke

Posted on 24. Oktober 201726. Mai 2023 by Stefan Draeger

Die Lambdas oder auch Closures genannt, sind mit der Version 8 von Oracle Java dazu gekommen. Und bieten dem Entwickler einen zusätzlichen Funktionsumfang, womit dieser noch weniger Code in noch kürzerer Zeit schreiben kann. Ob jedoch dieser Quellcode lesbarer und performanter als vorher ist, schauen wir uns am Ende des Beitrages an.

  • Beispiel mit einer ForEach Schleife
    • Performancevergleich
  • Neue Operationen an Listen
    • Intermediate Operation
      • filter
        • Mehrfachprüfung
      • map
      • sorted
    • Terminal Operation
      • collect
      • findFirst
      • findAny
      • min & max
  • Download

Beispiel mit einer ForEach Schleife

Hier nun ein paar kleine Beispiele anhand einer einfachen ForEach Schleife.

Gegeben ist immer folgende Liste:

 protected static List<String> names = new ArrayList<>();

  static {
       names = Arrays.asList(new String[] { 
                  "Stefan", "Udo", "Andrea", 
                  "Melanie", "Luca", "Elena", 
                  "Mia", "Hannah", "Florian", 
                  "Thomas", "Samuel", "Alexander",
                  "Daniel","Udo" });
  }

Vor der Version 5 von Java musste die Schleife wie folgt aussehen:

@Test
public void simpleForEachBeforeJava5() {    
   for (int i = 0; i < names.size(); i++) {
     logger.info(names.get(i));
   }
}

Ab der Version 5 konnte man dieses mit der erweiterten ForEach Schleife deutlich kürzer schreiben:

@Test
public void simpleForEachAfterJava5() {
   for (String name : names) {
     logger.info(name);
   }
}

Seit der Einführung der Lambdas könnte der Ausdruck wie folgt aussehen:

@Test
public void simpleForEachLambda() {
   names.forEach(name->{
     logger.info(name);
   });
}

Performancevergleich

Wie performant ist das ganze nun im Vergleich zu einem „normalen“ Ausdruck?
Im nachfolgenden möchte ich nun die Performance mit einem einfachen Mittel messen, ich nehme vor dem Starten der Methode einen Timestamp und danach einen Timestamp, der Rest ist simple Mathematik.

MethodeDauer*
 simpleForEachBeforeJava5() 8,6ms.
 simpleForEachAfterJava5() 1,4ms.
 simpleForEachLambda() 36,6ms.

*Durchschnittswert nach 10 Durchläufen.

Neue Operationen an Listen

Neben der Methode „forEach“ existieren noch weitere Operationen, dabei unterscheidet man zwischen einer „intermediate operation“ und einer „terminal operation“.

Intermediate Operation

Eine „intermediate operation“ hat immer einen Rückgabewert in Form eines modifizierten Streams auf diesen können dann weitere Operationen ausgeführt werden.

filter

Mit der Methode „filter“ kann man wie es sich erahnen lässt einen Stream nach einem bestimmten Kriterum (Predicate) filtern.
Da die Methode „filter“ einen manipulierten Stream zurückliefert kann man nun mit der Methode „findAny“ ein Optional zurückliefern. Wenn die Methode jedoch nichts findet, so wird kein NULL zurückgegeben, sondern ein Optional jedoch ohne Value. D.h. entweder man prüft am Optional ob dieses einen Wert hat oder aber man liefert ein NULL zurück.

Folgender Test liefert immer ein Optional zurück.

@Test
public void filterForStringPositiv() {
  Optional<String> name = names.stream().filter(n -> "Stefan".equalsIgnoreCase(n)).findAny();
  assertTrue(name.isPresent());
  logger.info(name.get());
}

Im nun folgenden Test wird nach einem Namen gesucht, welcher nicht in der Liste enthalten ist.
Im „echten“ Leben kennt man jedoch die Liste nicht, daher wird dieses programmatisch wie folgt gelöst:

@Test
public void filterForStringNegativNull() {
  String name = names.stream().filter(n -> "Robin".equalsIgnoreCase(n)).findAny().orElse(null);
  assertNull(name);
  logger.info(name);
}

Mit der Methode „orElse“ am Ende des Ausdrucks wird sichergestellt, dass, wenn kein Treffer gefunden wurde, NULL zurückgegeben wird.
Hier könnte auch ein beliebig anderes Objekt zurückgeliefert werden.

@Test
public void filterForStringNegativNull() {
  String name = names.stream().filter(n -> "Robin".equalsIgnoreCase(n)).findAny().orElse("Melanie");
  assertNotNull(name);
  logger.info(name);
}
Mehrfachprüfung

Möchte man in einem Ausdruck mehr als eine Prüfung durchführen, so erweitert man den Ausdruck, um eine geschweifte Klammer um darin die Prüfungen durchzuführen. Als Rückgabewert wird ein Boolean erwartet, „true“ wenn der Ausdruck zutrifft und „false“ wenn nicht.

@Test
public void filterForStringPositivMultiple() {
  Optional<String> name = names.stream().filter(n -> {
    if(n.equalsIgnoreCase("Stefan") && n.length()>5) {
      return true;
    }
    return false;
  }).findAny();
  assertTrue(name.isPresent());
  logger.info(name.get());
}

map

Mit der Methode „map“ kann man aus einem Ergebnis der filter Methode oder aber aus der gesamten Liste (mit „stream“) ein neues Objekt erstellen und zurückliefern.

Es soll nun für jeden Namen aus der Liste ein Objekt Person erstellt werden:

private class Person{
    
  private String name;

  public Person(String name) {
    super();
    this.name = name;
  }

  public String getName() { return name; }

  public void setName(String name) { this.name = name; }   
}

Bevor es die Methode map gab, wurde dieses wie folgt gelöst:

@Test
public void testMappingBeforeJava8() {
  logger.info("testMappingBeforeJava8()");
  List<Person> personen = new ArrayList<>();
  for(String name: names) {
    personen.add(new Person(name));
  }
  assertFalse(personen.isEmpty());
}

Mit der Methode map ist daraus nun ein einzeiliger Ausdruck geworden, welcher wie folgt, aussieht:

@Test
public void testMappingWithJava8Map() {
  logger.info("testMappingWithJava8Map()");
  List<Person> personen = names.stream().map(name -> new Person(name)).collect(Collectors.toList());
  assertFalse(personen.isEmpty());
}

sorted

Die Methode „sorted“ gibt einem die Möglichkeit eine Collection oder Map nach einem oder mehrere Kriterien zu sortieren. Maps können auch nach dem Key oder Value sortiert werden.
Wollte man „damals“ eine einfache Liste mit String Werten sortieren, so brauchte man nur

@Test
public void testSortedBeforeJava8() {
  logger.info("sortierte Ausgabe");
  Collections.sort(names);
  for(String name:names) {
    logger.info(name);
  } 
}

Ein String ist hier am einfachsten zu sortieren, da der Comparator welcher für die Methode „sort“ herangezogen wird die Methode toString() aufruft.

Seit Java8 kann man dieses nun eleganter schreiben.

@Test
public void testSortedWithJava8() {
  logger.info("unsortierte Ausgabe");
  names.stream().forEach(name -> logger.info(name));

  List<String> sortedNames = names.stream().sorted().collect(Collectors.toList());
  assertFalse(sortedNames.isEmpty());
  logger.info("sortierte Ausgabe");
  sortedNames.stream().forEach(name -> logger.info(name));
}

Es ist nun auch wieder ein einzeiliger Ausdruck geworden. Des Weiteren haben wir die ursprüngliche Liste nicht geändert und somit eine neue Liste mit Werten erzeugt.

Eine Liste ist jedoch ein sehr einfaches Beispiel, wie das mit einer Map aussieht, möchte ich nun an einem kleinen Beispiel erläutern:

@Test
public void testSortedMapWithJava8() {
  final String formatPattern = "Key: [%s] \t Value: [%d]";
  
  //Map mit den Beispielwerten erzeugen.
  final Map<String,Integer> personen = new HashMap<>();
  names.stream().forEach(name->{
    personen.put(name, getRandomAge());
  });
  logger.info("unsortierte Ausgabe");
  personen.entrySet().stream().forEach(entry ->{
    logger.info(String.format(formatPattern, entry.getKey(),entry.getValue()));
  });
    
  //Sortieren der Map nach entry.getValue()
  Map<String,Integer> sortedbyValue = new LinkedHashMap<>();
  personen.entrySet().stream().sorted(Map.Entry.<String,Integer>comparingByValue()).forEachOrdered(person -> sortedbyValue.put(person.getKey(), person.getValue()));
  logger.info("sortierte Ausgabe - (sortedByValue)");
  sortedbyValue.entrySet().stream().forEach(entry ->{
    logger.info(String.format(formatPattern, entry.getKey(),entry.getValue()));
  });

  //Sortieren der Map nach entry.getKey()
  Map<String,Integer> sortedByKey = new LinkedHashMap<>();
  personen.entrySet().stream().sorted(Map.Entry.<String,Integer>comparingByKey()).forEachOrdered(person -> sortedByKey.put(person.getKey(), person.getValue()));
  logger.info("sortierte Ausgabe - (sortedByKey)");
  sortedByKey.entrySet().stream().forEach(entry ->{
    logger.info(String.format(formatPattern, entry.getKey(),entry.getValue()));
  });    
}

Terminal Operation

Eine Terminal-Operation steht immer am Ende der Pipeline und erzeugt bzw. liefert einen Wert. Wir haben bereits  sollche Terminal Operations im ersten Kapitel verwendet Bsp. collect(Collectors.toList());“ oder auch „findAny();“.

collect

Mit der Methode „collect“ wird das Ergebnis einer Intermediate Operation gesammelt und Bsp. in eine java.util.Collection geschrieben.

@Test
public void testCollectwithJava8() {
  List<String> filteredNames = names.stream().filter(n -> { return n.equalsIgnoreCase("Stefan") && n.length()>5;}).collect(Collectors.toList());
  filteredNames.stream().forEach(name -> logger.info(name));
}

Es gibt auch die Möglichkeit die Werte zu einer Zeichenkette zusammen zuführen, dieses ist ein beliebtes Beispiel denn in einer For Schleife müsste man zuletzt immer prüfen ob der Zähler sich am Ende befindet und dann das Trennzeichen nicht anzeigen.

@Test
public void testCollectwithJavaJoin() {
  StringBuilder builder = new StringBuilder();
  for (int i = 0; i < names.size(); i++) {
    builder.append(names.get(i));
    if ((i + 1) < names.size()) {
      builder.append(",");
    }
  }
  logger.info(builder.toString());
}

Dieses wird nun deutlich vereinfacht:

@Test
public void testCollectwithJava8Join() {
  String filteredNames = names.stream().filter(n -> {return n.length() > 5;}).collect(Collectors.joining(","));
  logger.info(filteredNames);
}

Hier hat Google eine sehr gute Bibliothek erschaffen welche das noch mehr vereinfacht, zu finden ist diese im Maven Repository com.google.guava

findFirst

Wie die Bezeichnung es vermuten lässt, gibt die Methode findFirst den ersten Wert zurück. Als Rückgabe Wert erhält man ein Optional.

Gegeben sei also folgende java.util.List:

 protected static List<String> names = new ArrayList<>();

  static {
    names.add("Stefan");
    names.add("Udo");
    names.add("Andrea");
    names.add("Melanie");   
    names.add("Luca");   
    names.add("Elena");   
    names.add("Mia");   
    names.add("Hannah");   
    names.add("Florian");   
    names.add("Thomas");   
    names.add("Samuel");   
    names.add("Alexander");   
    names.add("Daniel");
    names.add("Udo");   
  }

Der Name „Udo“ ist hier 2-mal vorhanden, wir möchten jedoch nur den ersten Wert erhalten.

Mit der Methode FindFirst würde dieses wie folgt gelöst werden:

@Test
public void testFindFirstJava8() {
  Optional<String> name = names.stream().filter(n -> {
      return n.equalsIgnoreCase("Udo");
    }).findFirst();
  logger.info(name.get());
}

Mit den „Boardmitteln“ von Java würde dieses nicht viel weniger Quellcode werden.

@Test
public void testFindFirstJava() {
  for (String name : names) {
    if (name.equalsIgnoreCase("Udo")) {
      logger.info(name);
      break;
    }
  }
}

findAny

Die Methode findAny liefert in einem NICHT parallelen Stream den ersten Eintrag aus der Liste. Dieses ist jedoch nicht garantiert.

Mit Java8 wird dieses wiefolgt gelöst:

@Test
public void testFindAnyWithJava8() {
  Optional<Person> optional = persons.stream().findAny();
  assertTrue(optional.isPresent());
  logger.info(optional.get().toString());
}

Wen der Stream leer ist (nicht NULL) dann liefert diese Methode ein „null“ zurück d.h. man muss einen NULL Check auf das erwartete Optional durchführen.

@Test
public void testFindAnyWithJava8EmptyList() {
  List<Person> nullList = new ArrayList<>();
  Optional<Person> optional = nullList.stream().findAny();
  assertFalse(optional == null);
  logger.info("Der Wert ist NULL!");
}

Oder man „hängt“ hier wieder die Methode „orElse“ an das Ergebnis um ein definiertes Objekt zu erhalten.

@Test
public void testFindAnyWithJava8orElse() {
  List<Person> nullList = new ArrayList<>();
  Person person = nullList.stream().findAny().orElse(new Person("Steve","Jobs"));
  assertTrue(person.getFirstname().equalsIgnoreCase("Steve"));
  logger.info(person.toString());
}

min & max

Mit den Methoden min & max kann man den kleinsten bzw. den größtmöglichen Zahlenwert aus einem Stream ermitteln.

Gegeben sei also folgende Liste mit ganzen Zahlen:

private List<Integer> numbers = Arrays.asList(new Integer[] {10,6,1,0,4,5,3,15,2});

Die Methode „min“ erwartet einen Comparator welcher den Vergleich der Werte vornimmt.

private final Comparator<Integer> comparator = (num1, num2) -> Integer.compare(num1, num2);

Nun suchen wir den kleinsten Wert:

@Test
public void testMinJava8() {
  Optional<Integer> minValue = numbers.stream().min(comparator);
  assertTrue(0 == minValue.get());
}

Das Ermitteln des maximalen Werts erfolgt ähnlich:

@Test
public void testMaxJava8() {
  Optional<Integer> maxValue = numbers.stream().max(comparator);
  assertTrue(15 == maxValue.get());
}

Wollte man in einer Liste den kleinsten bzw. größten Wert ohne die Verwendung von Lambda suchen, so konnte man dieses wie folgt lösen:

@Test
public void testMinJavaBefore8() {
  int index = numbers.indexOf(Collections.min(numbers));
  Integer number = numbers.get(index);
  assertTrue(0 == number);
}
  
@Test
public void testMaxJavaBefore8() {
  int index = numbers.indexOf(Collections.max(numbers));
  Integer number = numbers.get(index);
  assertTrue(15 == number);
}

Download

Das gesamte Eclipse Projekt mit allen JUnit Testfällen gibt es hier zum Herunterladen:

Java8 Lambda SampleProjectHerunterladen

Schreibe einen Kommentar Antworten abbrechen

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

Fragen oder Feedback?

Du hast eine Idee, brauchst Hilfe oder möchtest Feedback loswerden?
Support-Ticket erstellen

Newsletter abonnieren

Bleib auf dem Laufenden: Erhalte regelmäßig Updates zu neuen Projekten, Tutorials und Tipps rund um Arduino, ESP32 und mehr – direkt in dein Postfach.

Jetzt Newsletter abonnieren

Unterstütze meinen Blog

Wenn dir meine Inhalte gefallen, freue ich mich über deine Unterstützung auf Tipeee.
So hilfst du mit, den Blog am Leben zu halten und neue Beiträge zu ermöglichen.

draeger-it.blog auf Tipeee unterstützen

Vielen Dank für deinen Support!
– Stefan Draeger

Kategorien

Tools

  • Unix-Zeitstempel-Rechner
  • ASCII Tabelle
  • Spannung, Strom, Widerstand und Leistung berechnen
  • Widerstandsrechner
  • 8×8 LED Matrix Tool
  • 8×16 LED Matrix Modul von Keyestudio
  • 16×16 LED Matrix – Generator

Links

Blogverzeichnis Bloggerei.de TopBlogs.de das Original - Blogverzeichnis | Blog Top Liste Blogverzeichnis trusted-blogs.com

Stefan Draeger
Königsberger Str. 13
38364 Schöningen
Tel.: 01778501273
E-Mail: info@draeger-it.blog

Folge mir auf

link zu Fabook
link zu LinkedIn
link zu YouTube
link zu TikTok
link zu Pinterest
link zu Instagram
  • Impressum
  • Datenschutzerklärung
  • Disclaimer
  • Cookie-Richtlinie (EU)
©2025 Technik Blog | Built using WordPress and Responsive Blogily theme by Superb
Cookie-Zustimmung verwalten
Wir verwenden Technologien wie Cookies, um Geräteinformationen zu speichern und/oder darauf zuzugreifen. Wir tun dies, um das Surferlebnis zu verbessern und um personalisierte Werbung anzuzeigen. Wenn Sie diesen Technologien zustimmen, können wir Daten wie das Surfverhalten oder eindeutige IDs auf dieser Website verarbeiten. Wenn Sie Ihre Zustimmung nicht erteilen oder zurückziehen, können bestimmte Funktionen beeinträchtigt werden.
Funktional Immer aktiv
Die technische Speicherung oder der Zugang ist unbedingt erforderlich für den rechtmäßigen Zweck, die Nutzung eines bestimmten Dienstes zu ermöglichen, der vom Teilnehmer oder Nutzer ausdrücklich gewünscht wird, oder für den alleinigen Zweck, die Übertragung einer Nachricht über ein elektronisches Kommunikationsnetz durchzuführen.
Vorlieben
Die technische Speicherung oder der Zugriff ist für den rechtmäßigen Zweck der Speicherung von Präferenzen erforderlich, die nicht vom Abonnenten oder Benutzer angefordert wurden.
Statistiken
Die technische Speicherung oder der Zugriff, der ausschließlich zu statistischen Zwecken erfolgt. Die technische Speicherung oder der Zugriff, der ausschließlich zu anonymen statistischen Zwecken verwendet wird. Ohne eine Vorladung, die freiwillige Zustimmung deines Internetdienstanbieters oder zusätzliche Aufzeichnungen von Dritten können die zu diesem Zweck gespeicherten oder abgerufenen Informationen allein in der Regel nicht dazu verwendet werden, dich zu identifizieren.
Marketing
Die technische Speicherung oder der Zugriff ist erforderlich, um Nutzerprofile zu erstellen, um Werbung zu versenden oder um den Nutzer auf einer Website oder über mehrere Websites hinweg zu ähnlichen Marketingzwecken zu verfolgen.
Optionen verwalten Dienste verwalten Verwalten von {vendor_count}-Lieferanten Lese mehr über diese Zwecke
Einstellungen anzeigen
{title} {title} {title}