Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass and use an arbitrary lambda functions as parameter [duplicate]

I have a good understanding about lambda functions in Lisp. Java seems not to have the same flexibility as Lisp. How do I have to think about lambdas in Java? Given the code below, how can I do that?

public class Draw {
    GraphicsContext gc;

    static void draw(double x, double y, double w, double h, boolean drawRect) {
        if (drawRect) {
            gc.fillRect(x, y, w, h);
        } else {
            gc.strokeRect(x, y, w, h);
        }
    }

    // How do I do that?
    static void drawWithLambda(double x, double y, double w, double h /**, lambda */) {
        lambda(x, y, w, h);
    }

    public static void main(String[] args) {
        draw(0, 0, 50, 50, false); // OK
        drawWithLambda(0, 0, 50, 50, GraphicsContext::fillRect); // Ok?
    }
}
like image 814
user2407434 Avatar asked Oct 07 '15 08:10

user2407434


2 Answers

Lambda in Java work in combination with the notion of functional interface.

The typical example is Function. Function is an functional interface whose functional method, apply, is a method that takes a single argument and return a result.

You can create your own functional interface, who would define a functional method taking 4 parameters and having no return type, like this:

@FunctionalInterface
interface RectangleDrawer {
    void draw(double x, double y, double w, double h);
}

(The FunctionalInterface annotation is not strictly necessary but it gives a clear intent).

You can then create a lambda that comply with the contract of this functional interface. The typical lambda syntax is (method arguments) -> (lambda body). In this example, it would be: (x, y, w, h) -> gc.fillRect(x, y, w, h). This compiles because the lambda declares 4 arguments and has no return type, so it can be represented as the functional method of RectangleDrawer defined before.

You example would become:

static GraphicsContext gc;

public static void main(String[] args) {
    draw(0, 0, 50, 50, (x, y, w, h) -> gc.fillRect(x, y, w, h));
    draw(0, 0, 50, 50, (x, y, w, h) -> gc.strokeRect(x, y, w, h));
}

static void draw(double x, double y, double w, double h, RectangleDrawer drawer) {
    drawer.draw(x, y, w, h);
}

In this particular case, it is possible to use a method reference to create the lambda, using the :: operator, which allows to write simpler code:

static GraphicsContext gc;

public static void main(String[] args) {
    draw(0, 0, 50, 50, gc::fillRect);
    draw(0, 0, 50, 50, gc::strokeRect);
}

static void draw(double x, double y, double w, double h, RectangleDrawer drawer) {
    drawer.draw(x, y, w, h);
}
like image 85
Tunaki Avatar answered Oct 11 '22 07:10

Tunaki


You must specify the type of a functional interface that the lambda method argument implements :

drawWithLambda(double x, double y, double w, double h, SomeFuncInterface lambda) {
    lambda.someMethod (x, y, w, h); // someMethod is the single method that
                                    // SomeFuncInterface implements
}

In order for this line to work

drawWithLambda(0, 0, 50, 50, GraphicsContext::fillRect);

the method fillRect of GraphicsContext must be compatible with the signature of the method of the SomeFuncInterface functional interface.

BTW, GraphicsContext::fillRect is a method reference, not a lambda expression. You can pass either lambda expressions, method references or any other implementation of the functional interface (regular class instance, anonymous class instance, etc...).

like image 36
Eran Avatar answered Oct 11 '22 09:10

Eran