6

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?
    }
}
Tunaki
  • 132,869
  • 46
  • 340
  • 423
  • 3
    Please read this article: http://stackoverflow.com/questions/13604703/how-do-i-define-a-method-which-takes-a-lambda-as-a-parameter-in-java-8 – psliwa Oct 07 '15 at 08:04

2 Answers2

4

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);
}
Tunaki
  • 132,869
  • 46
  • 340
  • 423
  • Thank you very much. I think I got it.For every function I want to pass as parameter, I habe to define am interface containig only one function definition with the same signature as the function I'd like to use. – user2407434 Oct 07 '15 at 08:48
  • 1
    @user2407434 You don't have to always define an interace, you can use the pre-existing ones in the [`java.util.function`](https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html) package. – Tunaki Oct 07 '15 at 08:49
3

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...).

Eran
  • 387,369
  • 54
  • 702
  • 768
  • 1
    `GraphicsContext::fillRect` won't compile because you can't make a static reference to the non-static method `fillRect`. You need to use `gc::fillRect` where `gc` is an instance of `GraphicsContext`. – Tunaki Oct 07 '15 at 08:27
  • 1
    @Tunaki My answer was general. I'm not familiar with `GraphicsContext` class, so I didn't know whether `fillRect` was static or not. – Eran Oct 07 '15 at 08:29