Why can we not use default methods in lambda expressions?

11,471

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.

Solution 5

Default methods can be accessed only with object references, if you want to access default method you'd have an object reference of Functional Interface, in lambda expression method body you won't have so can't access it.

You are getting an error incompatible types: Formula is not a functional interface because you have not provided @FunctionalInterface annotation, if you have provided you'll get 'method undefined' error, compiler will force you to create a method in the class.

@FunctionalInterface must have only one abstract method your Interface has that but it is missing the annotation.

But static methods have no such restriction, since we can access it with out object reference like below.

@FunctionalInterface
public interface Formula {

    double calculate(int a);

    static double sqrt(int a) {
        return Math.sqrt(a);
    }
}

public class Lambda {

    public static void main(String[] args) {
    Formula formula = (a) -> Formula.sqrt(a);
        System.out.println(formula.calculate(100));
    }

}
Share:
11,471

Related videos on Youtube

codegasmer
Author by

codegasmer

Updated on October 15, 2020

Comments

  • codegasmer
    codegasmer over 3 years

    I was reading this tutorial on Java 8 where the writer showed the code:

    interface Formula {
        double calculate(int a);
    
        default double sqrt(int a) {
            return Math.sqrt(a);
        }
    }
    

    And then said

    Default methods cannot be accessed from within lambda expressions. The following code does not compile:

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

    But he did not explain why it is not possible. I ran the code, and it gave an error,

    incompatible types: Formula is not a functional interface`

    So why is it not possible or what is the meaning of the error? The interface fulfills the requirement of a functional interface having one abstract method.

    • ZhongYu
      ZhongYu over 8 years
      If the interface defines some constant (static final) fields, these fields are not accessible (unqualified) in lambda body either. The lambda body simply is not in the context of a subclass of the interface
    • ZhongYu
      ZhongYu over 8 years
      you may define sqrt as static, and access it as a->Fomular.sqrt(...)
    • Captain Man
      Captain Man over 8 years
      @bayou.io Then it's not default though. :)
    • ZhongYu
      ZhongYu over 8 years
      @CaptainMan - yes, but the root problem really isn't about default. And if someone actually wants to accessible the method in a lambda expr, the method is very likely static in nature...
    • ZhongYu
      ZhongYu over 8 years
      note that even if static interface methods were designed to be inheritable, it is not gonna be inherited in the lambda body. Lambda conforms to the "shape" of the target type; other than that it's rather unrelated to the target type.
    • Captain Man
      Captain Man over 8 years
      @bayou.io I'm not saying your logic is wrong, I'm just saying OP was asking about default and was trying to imply that static methods cannot be default.
    • ZhongYu
      ZhongYu over 8 years
      @CaptainMan - I see. (or, in a twisted sense, we may say static is implicitly default, as in having a method body, or, being non-abstract...) (the default keyword here is more aesthetic in nature... javac didn't really need it to see that a method is non-abstract. well then, abstract keyword wasn't needed either:)
    • Brian Goetz
      Brian Goetz over 8 years
      The tutorial writer is confusing you by folding together multiple things. There's nothing specific about the interaction of lambdas and default methods. What's happening here is that the lambda body does not have access to any sort of this reference that could act as the receiver for the default method; the only names that are in scope are those that are in scope outside the lambda, plus the lambdas formals. If you let go of the unhelpful intuition that "lambdas are just inner classes" (which they're not), this ceases to be surprising.
  • Khanna111
    Khanna111 over 8 years
    please can you clarify " because the type of the this reference in the surrounding scope is not Formula"?
  • Erick G. Hagstrom
    Erick G. Hagstrom over 8 years
    A functional interface exists with or without the @FunctionalInterface annotation.
  • Sotirios Delimanolis
    Sotirios Delimanolis over 8 years
    @codegasmer Scope defines where you can use a name/identifier in a program. It is defined in the JLS, here. There are examples there.
  • Captain Man
    Captain Man over 8 years
    Erick is right. This is the same behavior as the @Override annotation, you don't need it to override a method, it simply fails to compile if the annotated method is not overriding something. @FunctionalInterface makes the thing it annotates not compile if it's not a functional interface, but if it still has exactly one abstract method the jvm/compiler will still treat it as a functional interface.
  • Paul Boddington
    Paul Boddington over 8 years
    I've edited my answer, hopefully making it clearer how this is the same inside and outside the lambda expression.
  • njzk2
    njzk2 over 8 years
    so how about Formula f = null; f = (a) -> f.sqrt(a * 100);?
  • Paul Boddington
    Paul Boddington over 8 years
    @njzk2 I'm not sure what you're asking, In that case you're doing f.sqrt not this.sqrt.
  • njzk2
    njzk2 over 8 years
    @PaulBoddington but since this can't refer to the lambda, f could, couldn't it? or is it not going to point at the lambda that was created, but rather statically resolved to null? or not going to compile because it should be final to be used in the lambda, and not resolved if it is not defined before it's use? (probably the last one, implicit final) (this is what I do when I need to recurse a lambda in python, naming it (which is a good sign that it should probably not be a lambda)).
  • njzk2
    njzk2 over 8 years
    @PaulBoddington indeed. just tested, Formula f; does not compile because f might not have been initialized and Formula f = null; does not compile because f must be final to be used in f.
  • Paul Boddington
    Paul Boddington over 8 years
    @njzk2 I'm not an expert on this so you're probably asking the wrong person. I literally only found out yesterday that this has the same meaning as in the surrounding scope, when I read this.cr.openjdk.java.net/~briangoetz/lambda/lambda-state-fin‌​al.html I was very surprised at the discovery.
  • a better oliver
    a better oliver over 8 years
    "it's not really clear to what concrete functional interface the lambda is going to be "converted" The lambda expression is an implementation of the Formula interface. That's as clear as it gets. "That means that this inside a lambda has no type yet" this has a type and it has nothing to do with the lambda expression itself. "you can't use methods of the future type" You can't use any of the interface's non-static methods, simply because inside a lambda expression you don't have access to the instance.
  • zapl
    zapl over 8 years
    @zeroflagL It's maybe not 100% accurate but works as mental model for me (that's why I put "You can imagine it this way" there). It also resembles a bit what invokedynamic does under the hood: not actually creating classes for lambdas but dynamically invoking methods.
  • ZhongYu
    ZhongYu over 8 years
    @njzk2 - recursion is a legit use case; it was on the drawing board, but ultimately not supported in the end. lots of workaround though to achieve it.
  • zapl
    zapl over 8 years
    Example here: gist.github.com/anonymous/e1d0e3d5f05688986018 - the code inside the lambda expression is compiled into a static method named private static void lambda$new$0(). In this context, the code there cannot reflect about it's own type because it doesn't have one. The same code is even used for two types so the compiler can't add the type at that point.
  • ZhongYu
    ZhongYu over 8 years
  • a better oliver
    a better oliver over 8 years
    You describe implementation details, the question at hand is about the language. The value of this is taken from the surrounding context. That's specified by the language. And that's all you need to know. The statement "this inside a lambda has no type yet," is simply wrong.
  • zapl
    zapl over 8 years
    @zeroflagL Correct. It's technically the wrong reason but gives me an explanation why this has to come from outside scope. Updated the Answer (again) to better reflect that. Feel free to edit it.
  • Holger
    Holger over 8 years
    @zeroflagL: actually, seeing lambda expressions as something without type (or something which might have a real functional type in future Java versions) that is converted to an interface type, is perfectly valid.