Why can we not use default methods in lambda expressions?

Solution 1:

It's more or less a question of scope. From the JLS

Unlike code appearing in anonymous class declarations, the meaning of names and the this and super keywords appearing in a lambda body, along with the accessibility of referenced declarations, are the same as in the surrounding context (except that lambda parameters introduce new names).

In your attempted example

Formula formula = (a) -> sqrt( a * 100);

the scope does not contain a declaration for the name sqrt.

This is also hinted at in the JLS

Practically speaking, it is unusual for a lambda expression to need to talk about itself (either to call itself recursively or to invoke its other methods), while it is more common to want to use names to refer to things in the enclosing class that would otherwise be shadowed (this, toString()). If it is necessary for a lambda expression to refer to itself (as if via this), a method reference or an anonymous inner class should be used instead.

I think it could have been implemented. They chose not to allow it.

Solution 2:

Lambda expressions work in a completely different way from anonymous classes in that this represents the same thing that it would in the scope surrounding the expression.

For example, this compiles

class Main {

    public static void main(String[] args) {
        new Main().foo();
    }

    void foo() {
        System.out.println(this);
        Runnable r = () -> {
            System.out.println(this);
        };
        r.run();
    }
}

and it prints something like

Main@f6f4d33
Main@f6f4d33

In other words this is a Main, rather than the object created by the lambda expression.

So you cannot use sqrt in your lambda expression because the type of the this reference is not Formula, or a subtype, and it does not have a sqrt method.

Formula is a functional interface though, and the code

Formula f = a -> a;

compiles and runs for me without any problem.

Although you cannot use a lambda expression for this, you can do it using an anonymous class, like this:

Formula f = new Formula() { 
    @Override 
    public double calculate(int a) { 
        return sqrt(a * 100); 
    }
};

Solution 3:

That's not exactly true. Default methods can be used in lambda expressions.

interface Value {
    int get();

    default int getDouble() {
        return get() * 2;
    }
}

public static void main(String[] args) {
    List<Value> list = Arrays.asList(
            () -> 1,
            () -> 2
        );
    int maxDoubled = list.stream()
        .mapToInt(val -> val.getDouble())
        .max()
        .orElse(0);
    System.out.println(maxDoubled);
}

prints 4 as expected and uses a default method inside a lambda expression (.mapToInt(val -> val.getDouble()))

What the author of your article tries to do here

Formula formula = (a) -> sqrt( a * 100);

is to define a Formula, which works as functional interface, directly via a lambda expression.

That works fine, in above example code, Value value = () -> 5 or with Formula as interface for example

Formula formula = (a) -> 2 * a * a + 1;

But

Formula formula = (a) -> sqrt( a * 100);

fails because it's trying to access the (this.)sqrt method but it can't. Lambdas as per spec inherit their scope from their surroundings, meaning that this inside a lambda refers to the same thing as directly outside of it. And there is no sqrt method outside.

My personal explanation for this: Inside the lambda expression, it's not really clear to what concrete functional interface the lambda is going to be "converted". Compare

interface NotRunnable {
    void notRun();
}

private final Runnable r = () -> {
    System.out.println("Hello");
};

private final NotRunnable r2 = r::run;

The very same lambda expression can be "cast" to multiple types. I think of it as if a lambda doesn't have a type. It's a special typeless function that can be used for any Interface with the right parameters. But that restriction means that you can't use methods of the future type because you can't know it.

Solution 4:

This adds little to the discussion, but I found it interesting anyways.

Another way to see the problem would be to think about it from the standpoint of a self-referencing lambda.

For example:

Formula formula = (a) -> formula.sqrt(a * 100);

It would seem that this ought to make sense, since by the time the lambda gets to be executed the formula reference must have already being initialized (i.e. there is not way to do formula.apply() until formula has been properly initialized, in whose case, from the body of the lambda, the body of apply, it should be possible to reference the same variable).

However this does not work either. Interestingly, it used to be possible at the beginning. You can see that Maurice Naftalin had it documented in his Lambda FAQ Web Site. But for some reason the support for this feature was ultimately removed.

Some of the suggestions given in other answers to this question have been already mentioned there in the very discussion in the lambda mailing list.