Einwickeln von Exceptions

Über das Einwickeln von Exceptions

Das Leben wäre schöner, gäbe es nur RuntimeExceptions – leider aber gibt’s auch Exceptions.

Eine übliche Lösung dieses Problems besteht bekanntlich darin, checked Exceptions in RuntimeExceptions einzuwickeln – und statt einer Exception dann diese RuntimeException zu werfen.

JPA z.B. wickelt alle SQLExceptions in JPA-Exceptions ein – und letztere sind RuntimeExceptions. Und auch das Springframework verwendet dieselbe Technik.

Dieses Einwickeln ist aber immer nervtötend. Betrachten wird folgendes Code-Fragment:

 

final Thread t = new Thread() {
    @Override 
    public void run() {
        System.out.println("Thread starts...");
        System.out.println("Simulating hard work...");
        try {
            Thread.sleep(1000);
        } 
        catch (final InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("Thread terminates...");
    }
};
t.start();
System.out.println("waiting...");
try {
    t.join();
} 
catch (final InterruptedException e) {
    throw new RuntimeException(e);
}
System.out.println("go on...")

(Man beachte, dass die run-Methode der Thread-Klasse keine checked-Exception werfen darf – wir müssen also einwickeln.)

Das Einwickeln erfordert immer sechs Zeilen.

Für die folgende Diskussion verwenden wir zwei Demo-Methoden, die jeweils eine checked Exception werfen können – eine Methode foo, die nichts zurückliefert, und eine bar-Methode, die ein int–Resultat liefert:

static void foo(final int x) throws Exception {
    if (x <= 0)
        throw new Exception("argument must be positive");
    System.out.println(x);
}
static int bar(final int x) throws Exception {
    if (x >= 0) 
        throw new Exception("argument must be negative");
    return -x;
}

Wir benutzen diese Methoden in einer Runnable– und einer Supplier-Implementierung. Bekanntlich darf weder die run-Methode von Runnable noch die get-Methode von Supplier eine checked-Exception werfen – wir wickeln diese Exception also in eine RuntimeException ein:

final Runnable runnable = () -> {
    try {
        foo(42);
    }
    catch (final Exception e) {
        throw new RuntimeException(e);
    }
};
runnable.run();
final Supplier<Integer> supplier = () -> { 
    try { 
        return bar(-42); 
    } 
    catch (final Exception e) { 
        throw new RuntimeException(e); 
    } 
}; 
System.out.println(supplier.get());

Nervtötend…

Wir entwickeln eine kleine Utilitiy-Klasse:

package jn.util;

public class TryCatch {

    public interface XRunnable {
        void run() throws Throwable;
    }

    public interface XSupplier<T> {
        T get() throws Throwable;
    }

    public static void wrapException(final XRunnable runnable) {
        try {
            runnable.run();
        }
        catch (final Throwable t) {
            throw new RuntimeException(t);
        }
     }

     public static <T> T wrapException(final XSupplier<T> supplier) {
         try {
             return supplier.get();
         }
         catch (final Throwable t) {
             throw new RuntimeException(t);
         }
     }
}

Die Klasse definiert zunächst “Exception”-verträgliche Versionen der beiden Standard-Interfaces Runnable und Supplier: die Interfaces XRunnable und XSupplier.

Sie definiert dann zwei statische wrapException-Methoden. Der ersten Methode wird ein XRunnable übergeben, der zweiten ein XSupplier. Die erste Methode ruft die run-Methode des XRunnables auf und wickelt eine Exception, die von dieser Methode geworfen werden kann, in eine RuntimeException ein. Die zweite Methode ruft die get-Methode des XSuppliers auf und liefert deren Resultat als eigenes Resultat zurück (und wickelt ein…)

Eine Anwendung dieser Methode kann zunächst einen statischen Import definieren:

import static jn.util.TryCatch.wrapException;

Die Thread-Anwendung, die oben vorgestellt wurde, kann nun wesentlich konziser formuliert werden:

final Thread t = new Thread() {
    @Override
    public void run() {
        System.out.println("Thread starts...");
        System.out.println("Simulating hard work...");
        wrapException(() -> Thread.sleep(1000));
        System.out.println("Thread terminates...");
    }
};
t.start();
System.out.println("waiting...");
wrapException(() -> t.join());
System.out.println("go on...");

Dasselbe gilt auch für das Runnable– und Supplier-Beispiel:

final Runnable runnable =
    () -> wrapException(() -> foo(42));
runnable.run();

final Supplier<Integer> supplier =
    () -> wrapException(() -> bar(-42));
System.out.println(supplier.get());

Bevor wir unsere Überlegungen weiterführen, ist zunächst eine kleine Refaktorierung angesagt. Wir können die erste wrapException-Methode auf die zweite wrapException-Methode abbilden:

public class TryCatch {

    // ...

    public static void wrapException(final XRunnable runnable) {
        wrapException(
             (XSupplier<Void>) () -> { runnable.run(); return null; });
    }

    public static <T> T wrapException(final XSupplier<T> supplier) {
        try {
            return supplier.get();
        }
        catch (final Throwable t) {
            throw new RuntimeException(t);
        }
    }
}

Exceptions sollten auch in spezifische RuntimeExceptions eingewickelt werden können. Wir erweitern unsere TryCatch-Klasse um zwei weitere wrapException-Methoden, denen zusätzlich zu dem XRunnable resp. dem XSupplier jeweils eine Function übergeben wird – ein exceptionWrapper, welchem ein Throwable übergeben wird und welcher dann eine RuntimeException liefern muss:

import java.util.function.Function;

public class TryCatch {

    // ...

    public static void wrapException(
            final XRunnable runnable,
            final Function<Throwable, RuntimeException> exceptionWrapper) {
        wrapException((XSupplier<Void>) () -> {
            runnable.run(); return null; }, exceptionWrapper);
    }

    public static <T> T wrapException(
            final XSupplier<T> supplier,
            final Function<Throwable, RuntimeException> exceptionWrapper) {
       try {
           return supplier.get();
       }
       catch (final Throwable t) {
           throw exceptionWrapper.apply(t);
       }
    }
}

Angenommen nun, die Anwendung definiert folgende RuntimeException-Klasse:

class MyException extends RuntimeException {
    private static final long serialVersionUID = 1L;

    public MyException(final String msg, final Throwable t) {
        super(msg, t);
    }

    @Override
    public String toString() {
         return this.getClass().getName() +
             " [" + this.getMessage() + ", " + this.getCause() + "]";
    }
}

Dann können die beiden neuen wrapException-Klassen wie folgt genutzt werden:

final Runnable runnable = () -> wrapException(
    () -> foo(42),
    e -> new MyException("Hello", e));
runnable.run();

final Supplier<Integer> supplier = () -> wrapException(
    () -> bar(42),
    e -> new MyException("World", e));
System.out.println(supplier.get());

Auch hier ist ein wenig Refaktorierung angesagt. Wir können eine der beiden alten wrapException-Methode auf die neue abbilden:

public static <T> T wrapException(final XSupplier<T> supplier) {
    return wrapException(supplier, e -> new RuntimeException(e));
}

public static <T> T wrapException(
        final XSupplier<T> supplier,
        final Function<Throwable, RuntimeException> exceptionWrapper) {
    try {
        return supplier.get();
    }
    catch (final Throwable t) {
        throw exceptionWrapper.apply(t);
    }
}

Hier noch einmal der komplette Quellcode von TryCatch:

package jn.util;

import java.util.function.Function;

public class TryCatch {

    public interface XRunnable {
        void run() throws Throwable;
    }

    public interface XSupplier<T> {
        T get() throws Throwable;
    }

    public static void wrapException(final XRunnable runnable) {
        wrapException(
            (XSupplier<Void>) () -> { runnable.run(); return null; });
    }

    public static void wrapException(
           final XRunnable runnable,
           final Function<Throwable, RuntimeException> exceptionWrapper) {
       wrapException(
           (XSupplier<Void>) () -> { runnable.run(); return null; },
           exceptionWrapper);
    }

    public static <T> T wrapException(final XSupplier<T> supplier) {
        return wrapException(supplier, e -> new RuntimeException(e));
    }

    public static <T> T wrapException(
            final XSupplier<T> supplier,
            final Function<Throwable, RuntimeException> exceptionWrapper) {
        try {
            return supplier.get();
        }
        catch (final Throwable t) {
            throw exceptionWrapper.apply(t);
        }
    }
}

 

Seminare zum Thema:

Clean Code – Professionelle Codeerstellung und Wartung

Java 8 / JDK8 Update

Java Erweiterungen II – Vertiefung

Weiterlesen

Java 9 und Jigsaw

Neue Tools in Java 9

Java 9 Modul-Konzept

Die wichtigste Neuerung von Java 9 ist zweifellos das neue Modul-Konzept – auch als “jigsaw” bezeichnet (JEPs 200, 201, 220, 260, 261, 281).

Das Modulkonzept verfolgt die Ziele “reliable configuration” und “strong encapsultion”. Was ist damit gemeint?

Im “alten” Java werden Klassen Pakete zugeordnet. Nur die als “public” gekennzeichneten Klassen eines Pakets können von Klassen anderer Pakete genutzt werden. Nicht-öffentliche Klassen können nur in anderen Klassen desselben Pakets verwendet werden.

Das Paket war bislang die höchste Organisations-Einheit, die Java kannte. Natürlich konnten class-Dateien zu jar-Dateien zusammengefasst werden – eine jar-Datei war bislang aber eben nichts anderes als ein gezipptes Verzeichnis mit class-Dateien. Eine jar-Datei hatte also kein dem Compiler oder der VM bekannte Java-Beschreibung. Das “Interface” einer jar war identisch mit ihrem Inhalt.

Daraus ergaben sich Probleme:

  • Angenommen, eine Anwendung enthält Klassen der Pakete jj.mod.pub und jj.mod.pri. In jj.mod.pub existiert die Klasse Foo, in jj.mod.pri existiert die Klasse Bar – eine Helper-Klasse, die von Foo genutzt wird. Bar muss nun public sein – ansonsten könnte sie von Foo nicht genutzt werden. Damit ist Bar natürlich auch für alle anderen Klassen nutzbar – obwohl sie nur als Helper-Klasse für Foo gedacht war. Wobei es völlig unerheblich ist, wie die class-Dateien zu jars zusammengefasst werden.
  • Klassen, die ein und demselben Paket zugeordnet waren, konnten auf verschiedene jar-Dateien aufgeteilt werden. Das trug nicht unbedingt zur Übersichtlichkeit bei.
  • Die Klassen, die zur Compilationszeit und zur Laufzeit herangezogen wurden, wurden über den CLASSPATH ermittelt. Enthielt der CLASSPATH zwei gleichnamige Klassen, so wurde die erste dieser Klassen herangezogen (die Änderung der Reihenfolge der Elemente des CLASSPATH konnte also eine sehr überraschende Wirkung haben).
  • Einer jar-Datei konnte man nicht ansehen, von welchen anderen jars sie abhängig war. Im Grunde war es immer Glücksache, alle benötigten jar-Dateien aufgefunden und bereitgestellt zu haben. Dieses Problem kann zwar durch die Benutzung von Build-Werkzeugen wie Apache Maven oder auch dem OSGi-Framework gelöst werden, aber eben nicht im Rahmen des Java-Standards.

Solche und ähnliche Probleme können vermieden werden, wenn das neue Modul-Konzept verwendet wird.

Ein Modul ist jar-Datei, die in ihrem Wurzelverzeichnis eine module-info.class enthält. Diese Datei wird vom Compiler aufgrund einer module-Info.java-Datei. Diese Datei enthält die Selbstbeschreibung des Moduls. Zu dieser Beschreibung gehören u.a. zwei wichtige Elemente: welche Pakete des Moduls sind von anderen Modulen nutzbar? – und von welchen anderen Modulen ist das Modul abhängig?

Wir greifen auf das obige Szenario zurück. Wir bauen ein Modul mit den Klassen Foo und Bar:

package jj.mod.pub;
 import jj.mod.pri;
 public class Foo {
 new Bar();
 }

package jj.mod.pri;
 public class Bar {
 }

Diese beiden Klasse sollen ein Modul namens jj.mod bilden. Wir schreiben folgende module-info.java:

module jj.mod {
 exports jj.mod.pub;
 }

Das Modul hat einen eigenständigen Java-Namen:  jj.mod. Solche Modul-Namen werden nach demselben Schema gebildet wie Paket-Namen. Der Modul-Name, die Namen der Pakete, die das Modul zusammenfasst, sind technisch völlig unabhängig voneinander – dennoch sollte natürlich ein gewisser Zusammenhang erkennbar sein.

Eine Modul jj.appl, welches dieses Modul nutzt, wird folgende Beschreibungsdatei enthalten:

module jj.appl {
 requires jj.mod;
 }

Die Klassen des jj.appl-Module können nun die Klassen aller Pakete des Moduls jj.mod nutzen, welche von diesem Modul exportiert werden – hier also nur die Klasse Foo:

package jj.appl;

public class Applicaiton {
 new jj.mod.pub.Foo();
 // new jj.mod.pri.Bar(); // illegal
 }

Module können zudem transitiv exportiert werden; und sie können ihre Pakete nur einer begrenzten Menge anderer Module zur Nutzung zur Verfügung stellen.

Mit diesem neuen Modul-Konzept wird der Modul-Pfad eingeführt, der den CLASSPATH ersetzen kann. Wird statt des CLASSPATH der Modul-Pfad verwendet, wird zudem verhindert, dass Pakete auf mehrere Module verteilt werden können.

Natürlich muss gewährleistet sein, dass auch “alte” jar-Dateien weiterhin genutzt werden können: sofern eine jar-Datei keine module-info.class enthält, exportiert sie implizit all ihre Pakete – und kann auf alle anderen jar-Dateien (auch auf modulare) zugreifen.

Und umgekehrt kann auch eine alte VM die neuen modularen jars nutzen – alte VMs ignorieren eine Datei namens module-info.class (“illegaler” Klassenname wegen des Bindestrichs).

Auch die rt.jar ist übrigens in mehrere Module zerlegt worden (eine Anwendung muss nur diejenigen Java-SE-Module an Bord haben, die sie tatsächlich benötigt).

Insbesondere bei der Erstellung großer Programmsysteme und bei der Erstellung von Bibliotheken ist das neue Modul-Konzept natürlich von grundlegender Bedeutung.

Publisher-Subscriber

Java 9 führt ein Publisher-Subscriber-Framework ein, welches der Reactive Streams Specification entspricht (java.utilconcurrent.Flow). Reactive Streams sind “asynchronous streams of data with non-blocking back pressure”. Ein reactive stream lässt sich grob als eine Kombination des Iterator- und des Observer-Patterns. Ein Iterator folgt dem Pull-Modell; ein Oberser folgt dem Push-Modell. Beim Flow-API fordert zunächst der Subscriber eine bestimmte Anzahl von zu verarbeitenden Objekten an (pull); dann stellt der Publisher diese Objekte dem Supplier in der von ihm gewünschten Anzahl zu (push). Das von Java 9 eingeführte Framework spezifiziert allerdings ausschließlich Interfaces – Implementierungen dieser Interfaces werden bereitgestellt z.B. von RxJava. (JEP 266)

jshell

Java 9 enthält nun eine jshell, ein interaktives Tool zur Auswertung von Deklarationen, Expressions und Anweisungen. Das Tool implementiert das REPL-Muster (Read-Eval-Print-Loop). Zwei kleine Beispiele:

jshell> Math.sqrt(2)

$1 ==> 1.4142135623730951

 

jshell> {     

   …> int a = 25;     

   …> int b = 15;     

   …> while (a != b) {     

   …> if (a > b) a -= b; else b -= a;     

   …> }     

   …> System.out.println(a);     

   …> }

5

Man kann nun also Java-Fragmente ausprobieren, ohne zunächst den Code komplett übersetzen und zu seiner Ausführung eine VM starten zu müssen (JEP 222).

Collection-Factory-Methods

Java 9 führt einige neue Factory-Methoden ein, mittels derer auf bequeme Weise immutable Collections erzeugt werden können (List.of, Set.of, Map.of, Map.ofEntries).

Erweiterungen der Klasse Process

Die Klasse Process erlaubt nun eine wesentlich erweiterte Kontrolle und Monitoring nativer Prozesse (diese Möglichkeiten waren bislang recht begrenzt). Neu sind in diesem Kontext u.a. die Interfaces ProcessHandle und ProcessHandle.Info.

Stream-API

Das Stream-API wurde erweitert. Neu sind hier insbesondere die Methoden dropWhile und takeWhile.

StackWalker

Informationen über den aktuellen Stack mussten bislang mit der Throwable-Methode getStackTrace abgefragt werden. Diese Methode liefert den kompletten Stack zurück. Mittels der in Java-9 eingeführten Klasse StackWalker können solche Informationen nun lazy ermittelt werden. Somit kann man z.B. erhebliche Kosten sparen, sofern man nur an den obersten Elementen des Stacks interessiert ist (JEP 259)

HTTP/2

Java 9 führt ein neues HTTP client API ein, das HTTP/2 und WebSocket implementiert und ersetzt somit das alte HttpURLConnection-API. Das neue API wird als Incubator-Modul ausgeliefert (es ist also noch offiziell Bestandteil der SE) (JEP 110)

Multi-release jars

Das jar-Dateiformat erlaubt nun die Koexistenz mehrerer Java-Release-spezifischer Versionen von class-Dateien (“multi-realease jar-files”). Dieses neue Feature verlangte natürlich auch die Erweiterung des jar-Tools (JEP 238)

GC

Als Garbage-Collector wird nun standardmäßig der G1 verwendet (dieser Collector eignet sich für große Heaps und mimimiert die Häufigkeit von Pausen). Er ersetzt somit den bislang verwendeten Parallel GC (JEP 248).

Compact Strings

Der Speicherverbrauch eines Programms wird maßgeblich von der Speicherung von Strings bestimmt. Strings wurden bislang mittels Arrays von chars implementiert – und ein char umfasst bekanntlich 2 Byte. In den meisten Fällen reicht aber ein einziges Byte zur Speicherung eines Zeichens aus. In Java 9 werden Strings nun – sofern möglich – platzsparender gespeichert (“compact strings”) (JEP 254).

Parser-API für JavaScript

Der Java-Script-Interpreter Nashorn wurde um ein Parser-API erweitert. Das API erlaubt die Visitor-basierte Inspektion eines JavaScript-AST-Baumes (JEP 236)

Diamond-Operator

Bislang war die Benutzung des “Diamond-Operators” beschränkt: bei der Definition von anonymen Klassen konnte er nicht verwendet werden. Diese Beschränkung ist nun aufgehoben worden (auch Kleinigkeiten sind wichtig…).

Für wen hat sich das Warten gelohnt? Für alle!

Was sich Entwickler jetzt noch wünschen? Das Modul-Konzept wird von einigen als zu statisch kritisiert. Kritisiert wird zuweilen auch, dass Module nicht geschachtelt werden können.

 

Seminare zu Java 9:

https://www.integrata-cegos.de/seminarangebot/anwendungsentwicklung/neuerungen-java-9-und-jigsaw/

Weiterlesen

Lambda Ausdrücke in Java 8 implementieren

Lambda Ausdrücke in Java 8

Die wichtigste Neuerung in Java 8 sind die sog. Lambda-Ausdrücke. Die Verwendung von Lambda-Ausdrücken führt in der Regel zu einem radikal verkürzten und konzisen Programmcode.

Lambda-Ausdrücke können überall dort verwendet werden, wo Interfaces implementiert werden, welche genau eine einzige abstrakte Methode spezifizieren. Solche Interfaces werden als funktionale Interfaces bezeichnet. Java 8 hat eine Vielzahl solcher Interfaces neu eingeführt: Supplier, Function, Operator, Predicate, Consumer etc., welche u.a. in den erweiterten Features der Standardbibliothek extensiv genutzt werden.

Im folgenden werden wir das Konzept der Lambda-Ausdrücke anhand einer einfachen Swing-Anwendung erläutern. Die Anwendung hat zwei JButtons, deren Betätigung eine bestimmte Aktion auslösen soll. Wir benötigen also für jeden JButton einen ActionListener. Wir implementieren diese ActionListener zunächst mittels anonymer Klassen.

Die Anwendung präsentiert sich dem Benutzer wie folgt:

Java 8 Anwendung

Wir definieren eine von JFrame abgeleitete Klasse:

// ...
public class MyFrame extends JFrame {

    private final JButton buttonOpen = new JButton("Open");
    private final JButton buttonSave = new JButton("Save");
    
    public MyFrame() {
        this.setLayout(new FlowLayout());
        this.add(this.buttonOpen);
        this.add(this.buttonSave);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.buttonOpen.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                MyFrame.this.onOpen();
            }
        });
        this.buttonSave.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                MyFrame.this.onSave();
            }
        });
        this.setBounds(100, 100, 300, 80);
        this.setVisible(true);
    }
    
    private void onOpen() {
        System.out.println("open...");
    }

    private void onSave() {
        System.out.println("save...");
    }
}

Der JButton-Methode addActionListener wird eine Instanz einer anonymen Klasse übergeben, die das (funktionale!) Interface ActionListener implementiert. Innerhalb der actionPerformed-Methode dieser Klasse kann die Instanz der äußeren Klasse via MyFrame.this angesprochen werden – und somit die onOpen-Methode der äußeren Klasse aufrufen zu können.

Die obige Formulierung enthält “boilerplate code”. Der Compiler weiß, dass an die addActionListener-Methode ein ActionListener übergeben werden muss (würden wir einen FocusListener übergeben, würde er sich beschweren:..). Also sollten wir new ActionListener() { und eine abschließende } weglassen können:

        this.buttonOpen.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                MyFrame.this.onOpen();
            }
        });

Der Compiler weiß weiterhin, dass das Interface ActionListener genau eine einzige Methode enthält: actionPerformed. Also sollten wir auch hier auf die explizite Nennung des Namens verzichten können:

this.buttonOpen.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                MyFrame.this.onOpen();
            }
        });

Was bleibt übrig?

this.buttonOpen.addActionListener( 
            (ActionEvent e) {
                MyFrame.this.onOpen();
            }
        );

Übrig bleibt nur die Parameterliste von actionPerformed und die Implementierung dieser Methode.

Der Compiler kann aufgrund dieser Kurzform wieder automatisch die ausführliche Form ableiten. Wir müssen ihm nur sagen, dass er eben dies tun soll – indem wir zwischen der Parameterliste und der Implementierung einen Pfeil einfügen:

        this.buttonOpen.addActionListener( 
            (ActionEvent e) -> {
                MyFrame.this.onOpen();
            }
        );

Diese Formulierung wird nun vom Compiler anstandslos übersetzt.

Können wir die Formulierung noch weiter vereinfachen?

Dem Compiler ist auch der Typ des Parameters bekannt: wir können uns also auf den Namen des Parameters beschränken:

        this.buttonOpen.addActionListener( 
            (e) ->; {
                MyFrame.this.onOpen();
            }
        );
;

Da nun die Parameterliste nurmehr aus dem Namen genau eines einzigen Parameters besteht, können wir auf die umschließenden Klammern verzichten:

        this.buttonOpen.addActionListener( 
            e -> {
                MyFrame.this.onOpen();
            }
        );

Und da auch die Implementierung nur aus einer einzigen Anweisung besteht, können wir auch die geschweiften Klammern streichen:

        this.buttonOpen.addActionListener( 
            e -> 
                MyFrame.this.onOpen()
        );

Und der Code wird noch schöner, wenn er in einer einzigen Zeile formuliert wird:

this.buttonOpen.addActionListener(e -> MyFrame.this.onOpen());

Der Ausdruck, der an addActionListener übergeben wird, wird als Lambda-Ausdruck bezeichnet. Wir können einen Lambda-Ausdruck als Kurzform der Instanziierung einer anonymen Klasse begreifen, welche ein funktionales Interface implementiert – wobei von dieser anonymen Klasse dann nurmehr die Parameterliste der Interface-Methode und deren Implementierung übrig bleibt (wobei Parameterliste und Implementierung durch einen Pfeil getrennt werden: die Parameter werden auf die Implementierung “abgebildet”).

Allerdings existieren auch Unterschiede zwischen anonymen Klassen und Lambdas:

In einer anonymen Klasse können z.B. weitere (Hilfs-)Methoden und Attribute definiert werden – in einem Lambda-Ausdruck ist dies natürlich nicht möglich.

In einer anonymen Klasse kann das Objekt dieser anonymen Klasse via this angesprochen werden – und das Objekt der “äußeren” Klasse via Outer.this (Outer sei der Name der äußeren Klasse). In einem Lambda wird auch via this das Objekt der äußeren(!) Klasse angesprochen: Outer.this und this sind hier identisch (insofern hätten wir in der obigen Lambda-Implementierung auch einfach notieren können: e -> this.onOpen().

Für Lambdas werden im Unterschied zu anonymen Klassen keine eigenen class-Dateien erzeugt. Der Bytecode existiert in der class-Datei der umschließenden Klasse.

Der Typ eine Lambdas kann als solcher nicht berechnet werden – die folgende Zuweisung ist daher illegal(!):

Object obj = e -> this.onOpen();

Der Typ eines Lambdas ergibt sich stets nur aus dem Ziel-Typ: dem Typ eines verlangten Parameters oder der Typ einer Variablen. Die folgende Zeile ist legal:

ActionListener l = e -> this.onOpen();

Dieses Feature wird auch als “Target Typing” bezeichnet.

Resultat: Wir können den obigen Programmcode für die einfache GUI radikal verkürzen:

// ...
public class MyFrame extends JFrame {

    private final JButton buttonOpen = new JButton("Open");
    private final JButton buttonSave = new JButton("Save");
    
    public MyFrame() {
        this.setLayout(new FlowLayout());
        this.add(this.buttonOpen);
        this.add(this.buttonSave);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.buttonOpen.addActionListener(e -> this.onOpen());
        this.buttonClose.addActionListener(e -> this.onClose());
        this.setBounds(100, 100, 300, 80);
        this.setVisible(true);
    }
    
    private void onOpen() {
        System.out.println("open...");
    }

    private void onSave() {
        System.out.println("save...");
    }
}

Ein “Lambda-Objekt” dient also nurmehr als Träger einer Methode. Nicht das Objekt ist von Belang, sondern nur die in ihm enthaltene Funktionalität. Wir können daher einen Lambda-Ausdruck auch als “Funktion” betrachten…

Weiterlesen

Scala Kontrollstrukturen implementieren

Scala Kontrollstrukturen implementieren

Scala enthält nur wenige Kontrollstrukturen. Aber wir können in Scala mit den Mitteln der Sprache selbst “eigene” Kontrollstrukturen implementieren, die so ausschauen, als seien sie in der Sprache selbst definiert. Wir können also die Sprache “erweitern”.

 

Im Folgenden werden wir eine repeat-until-Schleife implementieren. Diese wird in einigen aufeinander folgenden Schritten gezeigt. Die letzte Gestalt der Schleife wird sich wie folgt nutzen lassen:

 

import util.ControlC.repeat

var i = 0
 repeat {
 print(i + " ")
 i += 1
 } until i == 4

Die Ausgaben dieser Schleife sind nicht überraschend:

0 1 2 3

Hier die erste Version der Schleife:

object ControlA {

  class Until(block: Function0[Unit]) {
    def until(condition: Function0[Boolean]) = {
      block.apply()
      while (!condition.apply())
        block.apply()
    }
  }

  def repeat(block: Function0[Unit]): Until = {
    new Until(block)
  }
}

An repeat wird ein block übergeben (eine Referenz des Typs Function0[Unit]. Die Methode erzeugt ein Until-Objekt, dessen Konstruktor sie den block weiterreicht und liefert dieses Until-Objekt zurück. Auf das Until-Objekt kann dann die Methode until aufgerufen werden, welcher eine condition des Typs Function0[Boolean] übergeben wird. Die until-Methode ruft dann zunächst einmal die apply-Methode auf den übergebenen block auf. Dann wird die apply-Methode der condition aufgerufen. Solange diese jeweils true liefert, wird der block erneut ausgeführt.

Eine mögliche Anwendung:

import util.ControlA.repeat
    
    var i = 0
    repeat(new Function0[Unit] {
      override def apply() = {
        print(i + " ")
        i += 1
      }
    }).
    until(new Function0[Boolean] {
      override def apply(): Boolean = {
        i == 4
      }
    })

Die explizite Verwendung des Function0-Interfaces bedeutet zwar viel Schreibaufwand – aber hier wird zunächst einmal deutlich, was tatsächlich (auch in den späteren Varianten) passiert. Wir definieren und instanziieren zwei anonyme Klassen: die apply-Methode der ersten Klasse enthält den auszuführenden block, die apply-Methode der zweiten Klasse repräsentiert die condition.

Funktionale Schreibweise

Wir können die obige “objekt-orientierte” Schreibeweise natürlich auch durch die “funktionale” Schreibweise ersetzen.

Hier zunächst die zweite repeat-until-Variante:

object ControlB {

  class Until(block: () => Unit) {
    def until(condition: () => Boolean) = {
      block.apply()
      while (!condition.apply())
        block.apply()
    }
  }

  def repeat(block: () => Unit): Until = {
    new Until(block)
  }
}

Wobei wir statt block.apply() natürlich auch einfach block() schreiben könnten – und statt condition.apply() einfach condition().

Hier eine Anwendung (die allerdings auch mit ControlA funktionieren würde):

import util.ControlB.repeat
    
    var i = 0
    repeat(() => {
        print(i + " ")
        i += 1
    }).
    until(() => 
        i == 4
    )

Das sieht schon schöner aus…

Wir definieren eine dritte Variante der Schleife:

object ControlC {

  class Until(block: => Unit) {
    def until(condition: => Boolean) = {
      block
      while (!condition)
        block
    }
  }

  def repeat(block: => Unit): Until = {
    new Until(block)
  }
}

Die Parameter sowohl von repeat, vom Until-Konstruktor als auch von der until-Methode sind nun als Parameter definiert, die lazy ausgewertet werden. Man beachte: Hier wird z.B. block nicht mehr als ()=>Unit, sondern einfache als =>Unit definiert!

Eine mögliche Anwendung dieser neuen Varianten:

import util.ControlC.repeat

var i = 0
 repeat({
 print(i + " ")
 i += 1
 }).
 until(
 i == 4
 )

Wir können nun die runden Klammern durch geschweifte ersetzten (sofern eine Methode nur einen einzigen Parameter hat); und wir können until in der Infix-Schreibweise (als Infix-Operator) notieren – dann entfällt der Punkt vor until und die Klammern des until-Parameters:

    import util.ControlC.repeat
    
    var i = 0
    repeat({
        print(i + " ")
        i += 1
    }).
    until(
        i == 4
    )

Damit ist unser Ziel erreicht: wir haben eine repeat-until-Schleife gebaut, die so ausschaut, als sei sie Bestandteil der Sprache.

Allerdings können wir die Implementierung noch einmal verkürzen:

object ControlD {

  def repeat(block: => Unit) = {
    new AnyRef {
      def until(condition: => Boolean) = {
        block
        while (!condition)
          block
      }
    }
  }
}

Wir ersparen uns die Definition einer expliziten Until-Klasse und erzeugen einfach ein Objekt einer von AnyRef abgeleiteten anonymen Klasse.

 

Integrata Seminare zu Scala:

Scala Seminar

Weiterlesen

Funktionale Programmierung

Funktionale Programmierung und OO

Leichter, schlanker, schneller durch Funktionale Programmierung

Mit Java hat sich in den letzten 20 Jahren das Paradigma der objektorientierten Programmierung als Standard etabliert. Alternativ dazu wurde – im akademischen Raum – am Paradigma der funktionalen Programmierung gearbeitet, mit Sprachen wie Lisp, ML und anderen.

Seit einigen Jahren entwickeln sich neue Sprachgattungen wie Scala, Ceylon oder Dart, die versuchen, beide Paradigmen unter einem gemeinsamen Dach zusammenzufassen. Auch Java (wie auch C# und C++) wurde um funktionale Elemente erweitert. Welche Vorteile bringen diese Features den objekt-orientierten Sprachen?

  • Die funktionale Programmierung favorisiert immutable Elemente. Diese lassen sich beispielsweise einfacher verstehen als änderbare Elemente und bedürfen keiner Synchronisation. Zudem können iterative Algorithmen von rekursiven abgelöst werden.
  • Funktionen können als Werte behandelt werden und neben ihren eigenen Parametern auch Werte der “Umgebung”, in welcher sie erzeugt wurden, benutzen (Closure).
  • Die Typen von Funktionen (Typen der Parameter und Result-Typ) können häufig automatisch vom Compiler berechnet werden. Funktionen können “anonym” definiert werden. Solche Funktions-Ausdrücke werden als Lambdas bezeichnet.
  • Durch die partielle Auswertung (Currying) von Funktionen können Parameter reduziert werden um definierte Berechnungen auszuführen.

Funktionale Sprachen beschreiben das “Was”

Objektorientierte Sprachen verwenden das Konzept der imperativen Programmierung. Sie beschreibt das „Wie“ der Lösung. Funktionale Sprachen favorisieren dagegen den deklarativen Stil, der das “Was” beschreibt.

Die Verwendung funktionaler Konzepte führt zu einer drastischen Reduktion von Code. In Java 8 etwa können anonyme Klassen durch Lambdas ersetzt werden – aus fünf Zeilen Code kann eine einzige werden. Der Quelltext wird so leichter lesbar und auch leichter wartbar, was wiederum die Produktivität der Entwickler steigert.

Für objekt-orientierte Softwareentwickler bedeutet das zwar eine radikale Umstellung auf ein komplett anderes Erscheinungsbild der Features. Sobald ihnen jedoch die Vorteile, die die Verwendung funktionaler Elemente bieten, klar werden, möchten sie diese nicht mehr missen.

Integrata Seminare zur Funktionalen Programmierung:

Scala

Java 8 / JDK8 Update

Bildnachweis: pixabay CC0 Public Domain   by markusspiske

Weiterlesen