Klassen mit einer abstrakten Methode als funktionale Schnittstelle?

Als die Entwickler der Sprache Java die Lambda-Ausdrücke diskutierten, stand auch die Frage im Raum, ob abstrakte Klassen, die nur über eine abstrakte Methode verfügen, ebenfalls für Lambda-Ausdrücke genutzt werden können.[1] Sie entschieden sich dagegen, unter anderem deswegen, weil bei der Implementierung von Schnittstellen die JVM weitreichende Optimierungen vornehmen kann. Und bei Klassen wird das schwierig. Das liegt auch daran, dass ein Konstruktor umfangreiche Initialisierungen mit Seiteneffekten vornimmt (die Konstruktoren aller Oberklassen nicht zu vergessen), sowie Ausnahmen auslösen könnte. Gewünscht ist aber nur die Ausführung einer Implementierung der funktionalen Schnittstelle und kein anderer Code.

Es gibt nun im JDK einige abstrakte Klassen, die genau eine abstrakte Methode vorschreiben, etwa java.util.TimerTask. Solche Klassen können nicht über einen Lambda-Ausdruck realisiert werden; hier müssen Entwickler weiterhin zu Klassenimplementierungen greifen, und die kürzeste Lösung ist eine innere anonyme Klasse. Eigene Hilfsklassen können natürlich den Code etwas abkürzen, aber eben nur mit Hilfe einer eigenen Implementierung.

Wer abstrakte Methoden mit Lambda-Ausdrücken implementieren möchte, kann mit Hilfsklassen arbeiten. Denn wenn eine Hilfsklasse funktionale Schnittstellen einsetzt, so können Lambda-Ausdrücke wieder ins Spiel kommen, in dem die Implementierung der abstrakten Methode an den Lambda-Ausdruck weiterleitet. Nehmen wir das Beispiel für TimerTask und gehen zwei unterschiedliche Strategien der Implementierung durch. Mit Delegation sieht das so aus:

import java.util.*;

class TimerTaskLambda {

  public static TimerTask createTimerTask( Runnable runnable ) {
    return new TimerTask() {
        @Override public void run() { runnable.run(); }
    };
  }
 
  public static void main( String[] args ) {
    new Timer().schedule( createTimerTask( () -> System.out.println("Hi") ), 500 );
  }
}

Mit Vererbung erhalten wir:

public class LambdaTimerTask extends TimerTask {
  private final Runnable runnable;
    public LambdaTimerTask( Runnable runnable ) {
    this.runnable = runnable;
  }
   
  @Override public void run() { runnable.run(); }
}

Der Aufruf erfolgt dann statt createTimerTask(…) mit dem Konstruktor:

new Timer().schedule( new LambdaTimerTask( () -> System.out.println("Hi") ), 500 );

[1]              Früher wurde hier die Abkürzung SAM (Single Abstract Method) genutzt.

Über Christian Ullenboom

Ich bin Christian Ullenboom und Autor der Bücher ›Java ist auch eine Insel. Einführung, Ausbildung, Praxis‹ und ›Java SE 8 Standard-Bibliothek. Das Handbuch für Java-Entwickler‹. Seit 1997 berate ich Unternehmen im Einsatz von Java. Sun ernannte mich 2005 zum ›Java-Champion‹.

Schreibe einen Kommentar

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