Lambda-Ausdrücke seit Java1.8 SSJ Kapitel 21 Motivation Man möchte ausführbaren Code über eine Variable speichern und gegebenenfalls auch als Parameter an eine Methode oder Funktion übergeben. Programmcode soll wie Daten behandelt werden können! In funktionalen Programmiersprachen (z.B. Lisp, Haskell ..) ist dies ein Prinzip und Bestandteil der Sprache selbst. Java 8 erlaubt eine den funktionalen Sprachen ähnliche Syntax! Page 1 Funktionsinterface interface Function { int exec (int x); } Definition eines Interfaces mit nur einer Funktion oder Methode nennt man „FunctionalInterface“. Function f ; f = new Function() { int exec (int x) { return x * x; } Erzeugen einer anonymen Klasse und Zuweisung zur Variablen f. } f.exec(3); // ergibt 9 f = new Function() { int exec (int x) { return x + x; } } f.exec(3); Erzeugen einer neuen anonymen Klasse und Zuweisung zur Variablen f. // ergibt 6 Einfache Lambda-Ausdrücke Parameter -> Ausdruck Lambda-Ausdrücke bilden Parameter auf einen Wert ab. z.B. x -> x*x // Lambda-Ausdruck Lambda-Ausdrücke können an Functionalinterfaces zugewiesen werden. Die Typen werden durch die exec Methode festgelegt (hier alle int ). Funktionen könne wie Objekte zugewiesen werden. interface Function { int exec (int x); } Function f = x -> x*x ; f.exec(3); Die Typen des Parameters x als auch des Wertes des Ausdrucks müssen später aus dem Kontext vom Compiler erschlossen werden. // ergibt 9 Function f = x -> x*x ; wird vom Compiler automatisch ersetzt zu f = new Function() { int exec (int x) { return x*x; } } Page 2 Der Compiler erzeugt intern die entsprechende anonyme Klasse. Einfache Lambda-Ausdrücke (2) f = x ->x+x; // f.exec(10) ergibt 20 f = y ->13+y; // f.exec(10) ergibt 23 Beliebige Lambda-Ausdrücke die mit dem Functionalinterface von „Function“ kompatibel sind, können einer Variablen vom Typ Function zugewiesen werden. int[] map(Function f, int[] data) { z.B. Methode map erwartet einen Parameter vom Typ Function und kann deshalb auf die Methode exec von Function zugreifen. int[] result = new int[data.lengt]; for(int i = 0; i<data.length; i++) result[i] = f.exec(data[i]); return result; } int[] data = {1,2,3}; int[] result; result = map(x->x+1, data); // ergibt 2,3,4 result = map(x->x*x, data); // ergibt 1,4,9 Lambda-Ausdrücke können nun direkt an eine Methode die ein passendes Functionalinterface erwartet übergeben werden (hier Function). Lambda-Ausdrücke mit mehreren Parametern interface Function0 { int exec( ); } interface Function1 { int exec( int x, int y); } interface StringFunction { String exec(String s, int x ); } Function0 f0 = ()-> 42; // f0.exec() ergibt 42 Function1 f1 = (a,b)-> 2*a +a*b; // f2.exec(3,4) ergibt 18 StringFunction sf = (s,pos)-> s.substring(pos); // sf.exec(“Hallo Du”,6) ergibt “Du” Function1 f2 = (int a, int b)-> 2*a +a*b; // Parametertyp kann angegen werden. Function0 f3 = (int x)-> x*x; // Wenn ein Typ angegeben wird, dann // ist eine Klammer notwendig. Page 3 Lamda Syntax lambda = ArgList "->" Body ArgList = Identifier | "(" Identifier [ "," Identifier ] ")" | "(" Type Identifier [ "," Type Identifier ] ")" | "()" Body = Expression | "{" [ Statement ";" ]+ "}" ArgList -> Body x -> x + 1; (x) (x) -> -> x + 1; { return x + y; } ; (x,y) -> { x = 10 * x ; return x + y; } ; (int x, int y) -> x + y; () -> System.out.println("Hello World"); // Expression // Statement 7 Lambda-Ausdrücke mit Anweisungsteil interface Action0 { void exec (); } Action0 printHello = () -> System.out.print(“Hello”); interface Action2 { void exec (chr ch, int n); } Interface mit Methode ohne Parameter und Rückgabewert void . Interface mit Methode mit Übergabeparameter und Rückgabewert void . Action2 printLine = (ch, n ) -> { for(int i =0; i<n; i++) System.out.print(ch); System.out.println(); Lambda-Ausdruck mit Anweisungsteil in { }. } interface MyFunction { int exec (int a, int b); } MyFunction ggt = (x, y) -> { int rest = x%y; while(rest !=0) { x=y; y= rest ; rest = x%y; } return y; } int res = ggt.exec(24, 15); // ergibt 3 Page 4 Da die Methode exec vom Typ int ist muss ein Statement mit return einen Wert von Typ int zurückgeben. Methodenreferenzen interface Function2 { int exec (int a, int b); } Function2 f = Math::min ; f.exec(2,5); // ergibt 2 f = Math::min ; wird vom Compiler automatisch ersetzt zu Da der Lambda-Ausdruck nur eine namenlose Methode ist, darf man ihm in Java eine bekannte Methode zuweisen. Statische als ClassName::MethodName bei nicht statischen objectName::MethodName Der Compiler erzeugt intern die entsprechende anonyme Klasse und überprüft ob die im Interface definierten Parameter zur Methode passen. f = new Function2() { int exec (int a, int b) { return Math.min(a,b); } } java.util.function (seit Java 8) java.util.function stellt ein Vielzahl vordefinierter " Functionalinterfaces" unter Verwendung von Generics zur Verfügung. /** * Represents a function that accepts one argument and produces a result. * This is a functional interface whose functional method is apply(Object). * @param <T> the type of the input to the function * @param <R> the type of the result of the function @Name ist eine Annotation, welche auf den Ablauf des Programms keine Einfluss hat. */ Bestimmte Annotationen werden vom Compiler zur Überprüfung genutzt (hier, ob das interface auch nur eine abstracte Methode besitzt). @FunctionalInterface public interface Function<T, R> { R apply(T t); } Verwendung Function<Integer, Integer> pow = (i) -> i * i; Function<Integer, Integer> abs = Math::abs; System.out.println(pow.apply(-5)); System.out.println(abs.apply(-5)); Page 5 // Ausgabe: 25 // Ausgabe: 5 java.util.function @FunctionalInterface @FunctionalInterface public interface Predicate<T> { boolean test(T t); } Anwendungsbeispiel public static int mySum(List<Integer> numbers, Predicate <Integer> p) { int out = 0; for (int i : numbers) { if (p.test(i)) { out += i; } } return out; } int sum = mySum (list, x -> x % 2 == 0); Interfaces mit "default" Methoden Bisher (vor Java8) konnten Interfaces keine implementierte Methoden enthalten. Wollte man aber ein Interface erweitern, musste jede Klasse, die davon erbt, die neue Methode implementieren. Jedes Programm, dass für eine ältere Version geschrieben war, konnte daher nicht mehr funktionieren. Default Methoden geben die Möglichkeit, Interfaces um grundlegende Funktionen zu erweitern. Zusätzlich ermöglichen sie es, Interfaces wie das FunctionalInterfaces mit zusätzlichen Hilfs-Methoden auszustatten. @FunctionalInterface public interface Predicate<T> { boolean test(T t); } default Predicate<T> negate() { return (t) -> !test(t); } } Eine default Methode kann keine Member Variablen verändern und ist auf die Verwendung der abstracten Interface-Methoden angewiesen. Page 6 java.util.function Predicate (siehe API) Das Predicate FunctionalInterface wie in der API beschrieben. @FunctionalInterface public interface Predicate<T> default static Predicate<T> <T> Predicate<T> default Predicate<T> and(Predicate<? super T> other) Returns a composed predicate that represents a short-circuiting logical AND of this predicate and another. isEqual( Object targetRef ) Returns a predicate that tests if two arguments are equal according to Object.equals(Object,Object). or(Predicate<? super T> other) Returns a predicate that represents the logical negation of this predicate. default Predicate<T> negate() Returns a composed predicate that represents a short-circuiting logical OR of this predicate and another. boolean test(T t) Evaluates this predicate on the given argument. Beispiel 1 zu Predicate import java.util.function.*; class MyPredicateTest { public static void main ( String[] args){ Predicate<Integer> greaterThanTen = (i) -> i > 10; Predicate<Integer> lowerThanTwenty = (i) -> i < 20; boolean result = greaterThanTen.or(lowerThanTwenty).negate().test(15); System.out.print(result ); } } Page 7 Beispiel 2 zu Predicate import java.util.function.*; class MyPredicateTest { static void process(int number, Predicate<Integer> predicate) { if (predicate.test(number)) { System.out.println("Number " + number + " was accepted!"); } } public static void main ( String[] args){ process(10, (i) -> i > 7); } } Zur Erinnerung: FunctionPlotter mit abstracter Funktion aus der Vorlesung zum AWT. jawa.awt.Frame jawa.awt.WindowListener INTERFACE FunctionPlotter WindowSize; XYCoordinates; FunctionPlotter; paint; function; getPixel_fromXY; SinXPlotter XCosXPlotter TanXPlotter double function (double x ); double function (double x ); double function (double x ); SinXPlotter XCosXPlotter TanXPlotter main; main; main; Page 8 ... .. . Beispiel: Code der abstrakten Klasse FunctionPlotter import java.awt.* ; public abstract class FunctionPlotter extends java.awt.Frame { private int noSamples; private double minx; ........... private int winx; private int winy; // samples to be plotted // minimal plotted x-Value /** This method is invoked automatically by the * runtime-environment whenever the contents * of the window is displayed on the screen and * (once everytime a redraw event occures) * plotting of the graph of f(x) is implemented here */ public void paint (Graphics g) { // horiz. window size in pixel // vertical window size in pixel int i; int x1,x2,y1,y2; double step = (maxx - minx) / (noSamples -1); double x = minx; g.setColor( Color.red); // Constructor for the diplay domain public FunctionPlotter ( int _noSamples, int _winx, int _winy, double _minx, double _maxx, double _miny, double _maxy) { ............. setSize (winx,winy); setVisible(true); } x2 = getPixelXfromWorldX( x ); y2 = getPixelYfromWorldY( f( x ) ); for(i = 1; i< noSamples; i++){ x+= step; x1 = x2; y1 = y2; x2 = getPixelXfromWorldX ( x ); y2 = getPixelYfromWorldY ( f(x) ); // Transforanitons from World to Pixel Koordinates public int getPixelXfromWorldX( double x){ return (int) ( (x - minx) / (maxx - minx)* winx) ; } public int getPixelYfromWorldY( double y){ ...... } /** * Abstract method to be implemented in child classes * to evaluate the function to be plotted */ public abstract double f ( double x); g.drawLine(x1,y1,x2,y2); } } } Beispiel: Rahmen zum Zeichnen reeller Funktionen Um eine Funktion, wie z. B. x x*cos(x) plotten zu können, mussten wir eine Klasse definieren, • die FunctionPlotter erweitert, • und in der f durch die gewünschte Funktion implementiert ist. Außer einem entsprechenden Konstruktor muss in dieser Klasse keine weitere Methode definiert sein. Page 9 früher heute Die Funktion wird dem Konstruktor der Plotter-Klasse übergeben Implementierte abstrakte Klasse /** * Concrete Class which implements function f() * to calculate x times cos(x) */ /** * Class uses FunctionalPlotter-Class to plott various functions */ public class Application { public class XCosXPlotter extends FunctionPlotter { public XCosXPlotter( int nSamples, int _winx, int _winy, double _minx, double _maxx, double _miny, double _maxy ) { super( nSamples, _winx,_winy, _minx, _maxx, _miny,_maxy); } public double f(double x) { return x* Math.cos(x); } public static void main( String args[]) { new XCosXPlotter( 50, 300,200, 0.0, 10.0, -1.5, 1.5); } public static void main( String args[]) { new FunctionalPlotter( x-> x*Math.cox(x), 50, 300,200, 0.0, 10.0, -1.5, 1.5); } new FunctionalPlotter( x-> x*Math.sin(x), 50, 300,200, 0.0, 10.0, -1.5, 1.5); } } FunctionalPlotter mit Lambda-Ausdruck initialisieren. jawa.awt.Frame jawa.awt.WindowListener INTERFACE FunctionalPlotter WindowSize; Die Klasse FunctionalPlotter hat nun ein Funktion f vom Typ des FunctionalInterfaces Function<Double,Double>. XYCoordinates; FunctionalPlotter; paint; Function; Über den Konstruktor FunctionalPlotter( Function<Double,Double>, ……) kann die Klasse mit beliebigen Funktionen initialisiert werden. getPixel_fromXY; Application main; In einer beliebigen Anwendung kann nun die Klasse FunctionalPlotter genutzt werden und mit einer beliebigen Funktion, über einen Lambda-Ausdruck im Konstruktor (wie z. B. new FunctionalPlotter( x x*cos(x), ……) ) aufgerufen werden . Page 10 Früher mit abstrakten f neu mit FunctionalInterface import java.awt.* ; import java.awt.* ; import.util.function.Function public abstract class FunctionPlotter extends java.awt.Frame { public class FunctionalPlotter extends java.awt.Frame { private int noSamples; // private double minx; ........... private int winx; private int winy; samples to be plotted // minimal plotted x-Value // horiz. window size in pixel // vertical window size in pixel private Function<Double,Double> f; private int noSamples; // samples to be plotted private double minx; // minimal plotted x-Value ........... private int winx; // window size in pixel private int winy; // vertical window size in pixel // Constructor for the diplay domain public FunctionPlotter ( int _noSamples, int _winx, int _winy, double _minx, double _maxx, double _miny, double _maxy) { ............. setSize (winx,winy); setVisible(true); } // Transforanitons from World to Pixel Koordinates public int getPixelXfromWorldX( double x){ return (int) ( (x - minx) / (maxx - minx)* winx) ; } public int getPixelYfromWorldY( double y){ ...... } /** // Constructor for the diplay domain public FunctionalPlotter (Function<Double,Double> _f, int _noSamples, int _winx, int _winy, double _minx, double _maxx, double _miny, double _maxy) { ............. f=_f; setSize (winx,winy); setVisible(true); } // Transforanitons from World to Pixel Koordinates public int getPixelXfromWorldX( double x){ return (int) ( (x - minx) / (maxx - minx)* winx) ; } public int getPixelYfromWorldY( double y){ ...... } * Abstract method to be implemented in child classes * to evaluate the function to be plotted */ public abstract double f ( double x); Früher mit abstrakten f /** This method is invoked automatically by the * runtime-environment whenever the contents * of the window is displayed on the screen and * (once everytime a redraw event occures) * plotting of the graph of f(x) is implemented here */ public void paint (Graphics g) { neu mit FunctionalInterface /** This method is invoked automatically by the * runtime-environment whenever the contents * of the window is displayed on the screen and * (once everytime a redraw event occures) * plotting of the graph of f.apply(x) is implemented here */ public void paint (Graphics g) { int i; int x1,x2,y1,y2; double step = (maxx - minx) / (noSamples -1); double x = minx; g.setColor( Color.red); int i; int x1,x2,y1,y2; double step = (maxx - minx) / (noSamples -1); double x = minx; g.setColor( Color.red); x2 = getPixelXfromWorldX( x ); y2 = getPixelYfromWorldY( f( x ) ); x2 = getPixelXfromWorldX( x ); y2 = getPixelYfromWorldY( f.apply( x ) ); for(i = 1; i< noSamples; i++){ x+= step; x1 = x2; y1 = y2; x2 = getPixelXfromWorldX ( x ); y2 = getPixelYfromWorldY ( f(x) ); for(i = 1; i< noSamples; i++){ x+= step; x1 = x2; y1 = y2; x2 = getPixelXfromWorldX ( x ); y2 = getPixelYfromWorldY ( f.apply(x) ); g.drawLine(x1,y1,x2,y2); g.drawLine(x1,y1,x2,y2); } } } } } } Page 11