Lazy Evaluation in Java mit Lambdas

04.05.2017

Mit Java 8 haben die Lambdas Einzug in die Sprache gehalten. Am Beispiel von Logging zeigt dieser Artikel, wie man diese anonymen Funktionen für eine Lazy Evaluation einsetzen kann. Ziel ist es dabei, teure Operationen nur im Bedarfsfall auszuführen.

Ein typischer Anwendungsfall beim Loggen ist das Schreiben von Datenstruktur-Inhalten ins Log. Dazu wird aus dem entsprechenden Objekt eine String-Repräsentation generiert, die anschließend geloggt wird. Die String-Repräsentation soll natürlich nur dann erzeugt werden, wenn der Logger für den gewählten Loglevel aktiv ist. Dies gilt insbesondere, wenn es sich um große bzw. komplexe Datenstrukturen handelt.

Prüfung des Loglevels

Mit frühen Versionen von Logging-Frameworks ist dafür noch eine explizite Abfrage des Loglevels im Client-Code notwendig:

if (LOGGER.isDebugEnabled()) {
   LOGGER.debug("Data read from DB: " + myComplexDataStructure);
};

Nur wenn das Logging so eingestellt ist dass DEBUG-Nachrichten geloggt werden, wird die Log-Nachricht erzeugt. Und nur dann wird toString()-Methode des myComplexDataStructure-Objekts aufgerufen (implizit, infolge der String-Verkettung mittels + ).

Diese Vorgehensweise ist jedoch mit einigen Nachteilen behaftet:

  • Sie verletzt das DRY-Prinzip (DRY = „Don’t repeat yourself“), da die Bedingungsprüfung jedes Mal wenn es notwendig ist im Client-Code wiederholt werden muss.
  • Sie ist fehleranfällig für Codeänderungen. Wird z.B. ein anderer Loglevel gewählt, besteht die Gefahr, dass man versehentlich nur eine von zwei anzupassenden Stellen ändert.
  • Es kann passieren, dass Produktivcode versehentlich in den if-Block rutscht. Dieser Code wird nicht mehr ausgeführt, sobald der Loglevel auf INFO oder höher gestellt wird, was in der Regel zu schwer nachvollziehbaren Bugs führt.

 

Logging mit Parametern

Als Abhilfe wurde in späteren Versionen der Logging-APIs die Möglichkeit geschaffen, der Logmethode Parameter zu übergeben, die in die Log-Nachricht hineinformatiert werden:

LOGGER.debug("Data read from DB: {}", myComplexDataStructure);

Die Implementierung der debug-Methode sorgt dafür, dass myComplexDataStructure.toString() nur aufgerufen wird, wenn der eingestellte Loglevel DEBUG-Nachrichten zulässt. Diese Syntax ist um einiges kompakter und weitaus weniger anfällig für Fehler.

Doch nicht immer ist parametrisiertes Loggen möglich. Im folgenden Fall haben wir einen Servlet-Request, dessen Parameter und die jeweiligen Werte geloggt werden sollen. Hierzu verwenden wir die Hilfsmethode requestParametersAndValuesToString:

protected void doGet(HttpServletRequest request, HttpServletResponse response) {

   LOGGER.debug("The request parameters are: {}", requestParametersAndValuesToString(request));

   // ... die eigentliche Geschäftslogik
}

Die Problematik daran ist, dass die Methode requestParametersAndValuesToString(request) ausgeführt werden muss, bevor der Aufruf von LOGGER.debug(…) erfolgen kann. Um das zu vermeiden müssten wir wieder auf die Loglevel-Prüfung zurückgreifen, die allerdings wie oben gezeigt alles andere als optimal ist.

Lambdas sind die Rettung!

Moderne Versionen von Logging-Frameworks, die Java 8 und damit Lambdas unterstützen, bieten die Möglichkeit, ein Lambda in die Logmethode zu geben, das die Lognachricht erzeugt. So sieht beispielsweise eine der Signaturen des Loggers aus log4j aus:

public void debug(Supplier<?> msgSupplier) { ... }

Beim Supplier handelt es sich um ein funktionales Interface (@FunctionalInterface) das es uns ermöglicht, die Methode mit einem Lambda aufzurufen. Es beinhaltet lediglich eine Methode namens get(), die in unserem Fall die zu loggende Nachricht, zurückliefern soll.

Der loggende Client-Code definiert das Lambda demnach wie folgt:

LOGGER.debug(() -> "The request parameters are: " + requestParametersAndValuesToString(request));

Auch wenn es auf den ersten Blick so aussehen mag, wird die String-Verkettung im Gegensatz zum Beispiel weiter oben nicht vor dem debug(…)-Aufruf vorgenommen. Erst innerhalb der debug(…)-Methode, und zwar nachdem das Logging-Framework die Loglevel-Prüfung vorgenommen hat, ruft das Framework die get()-Methode des Lambdas auf (das durch das funktionale Interface eine Supplier-Instanz ist), und erhält so die zu loggende Nachricht. Die String-Verkettung findet erst zum Zeitpunkt des get()-Aufrufs statt.

Unterstützung durch Logging-Frameworks

Aktuell unterstützen nur log4j sowie die in Java enthaltene java.util.logging-API das Loggen mit Lambdas. Wer logback verwenden möchte oder mit Hilfe von SLF4J von einer konkreten Logging-Implementierung unabhängig bleiben will, muss sich leider noch gedulden bzw. sich mit selbstimplementierten Lambda-fähigen Hilfsmethoden behelfen:

LoggingHelper.debug(LOGGER, () -> "The request parameters are: " + requestParametersAndValuesToString(request));

Fazit

Lambdas helfen dabei, speziell fürs Logging benötigte Operationen nur dann auszuführen wenn es tatsächlich notwendig ist. Gleichzeitig bleibt die Syntax der Logaufrufe kompakt und fehlertolerant. Doch obwohl Java 8 nun schon recht lange verfügbar ist, werden Lambdas leider noch nicht von allen Logging-Frameworks unterstützt.

 

Zurück zur Übersicht

Kommentar verfassen

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

*Pflichtfelder

*