Lambda expression vs method reference
Asked Answered
O

6

173

IntelliJ keeps proposing me to replace my lambda expressions with method references.

Is there any objective difference between both of them?

Omega answered 30/6, 2014 at 10:14 Comment(13)
It is just the same things, BUT don't you find beautiful something like files.stream().map(File::getName)?Mcgrath
"looks like" is not an argument here, I think.Mcgrath
Of course! But "don't you find" either... It is a matter of taste, I was more worried about more technical aspects. In fact, as you already said that they are the same, that is a good answer to me. Anyway, as IntelliJ proposes it, I guess that it is generally more appreciated to see a method reference than a lambda (not for me, though).Omega
I guess using lambda is a bit more overhead for creating the lambda (anonymous function class and instance) and calling it. But anyway if you just want to call an exiting method, I'd go with the method pointer, too. It's just cleaner. If you use lambda, you just have to visually parse the expression to check whether there's maybe a - hidden in there or stuff.Active
I'd wager that java does the same thing with method reference.Pouter
It seems then that the only discussion is about syntax, and that seems rather subjective (with some exceptions).Omega
Overhead of either is an implementation detail and is bound to be quite variable, tending towards zero as the lambda support evolves in HotSpot.Vibratory
The code of a lambda expression is compiled to a synthetic method while method references work without (exception: special constructs like Type[]::new). The anonymous class generated at runtime will be the same. The JRE does not make any difference between them. So using a method reference will save you one method in your compiled code, on the other hand, you can’t stop at them when doing step-by-step debugging…Sulphonate
Now the question is going to be shut down... Too bad for all the users like me that don't know the difference between lambdas and references, even if there isn't any decisive one. I also wonder why nobody dared answering that? That is the right answer to me.Omega
Answering a two years old closed question is probably a bad idea but for those who ACTUALLY READ the question this is what Oracle's tutorial (docs.oracle.com/javase/tutorial/java/javaOO/…) says: Method references ... are compact, easy-to-read lambda expressions for methods that already have a name.Hendley
Well I'm a bit puzzled that nobody mentioned this MAJOR difference: expression setCallback(object::method); throws NPE if object == null, but setCallback(() -> object.method()); does not!Galen
@Holger: A few words seem missing from your comment. Can you elaborate on how each is compiled and subsequently run ? If they're the same (or nearly so) in performance, then I'd usually tend to go for a method reference as the code statement's purpose is clearer.Reflate
@Reflate the format of a comment is not sufficient to describe the details (more than my previous comment already did). You may read this and that, if you are interested in more details. But the takeaway regarding your question is simple: there is no significant difference, so if you prefer method references, go for it. Just don’t try to bend code towards using them excessively, i.e. when a lambda is much simpler, don’t stay away from using it.Sulphonate
B
297

Let me offer some perspective on why we added this feature to the language, when clearly we didn't strictly need to (all methods refs can be expressed as lambdas.)

Note that there is no right answer. Anyone who says "always use a method ref instead of a lambda" or "always use a lambda instead of a method ref" should be ignored.

This question is very similar in spirit to "when should I use a named class vs an anonymous class"? And the answer is the same: when you find it more readable. There are certainly cases that are definitely one or definitely the other but there's a host of grey in the middle, and judgment must be used.

The theory behind method refs is simple: names matter. If a method has a name, then referring to it by name, rather than by an imperative bag of code that ultimately just turns around and invokes it, is often (but not always!) more clear and readable.

The arguments about performance or about counting characters are mostly red herrings, and you should ignore them. The goal is writing code that is crystal clear what it does. Very often (but not always!) method refs win on this metric, so we included them as an option, to be used in those cases.

A key consideration about whether method refs clarify or obfuscate intent is whether it is obvious from context what is the shape of the function being represented. In some cases (e.g., map(Person::getLastName), it's quite clear from the context that a function that maps one thing to another is required, and in cases like this, method references shine. In others, using a method ref requires the reader to wonder about what kind of function is being described; this is a warning sign that a lambda might be more readable, even if it is longer.

Finally, what we've found is that most people at first steer away from method refs because they feel even newer and weirder than lambdas, and so initially find them "less readable", but over time, when they get used to the syntax, generally change their behavior and gravitate towards method references when they can. So be aware that your own subjective initial "less readable" reaction almost certainly entails some aspect of familiarity bias, and you should give yourself a chance to get comfortable with both before rendering a stylistic opinion.

Barrada answered 30/6, 2014 at 15:39 Comment(2)
Just wanted to say thanks for this excellent answer. The last paragraph in particular resonated with me, and has led me to be more accepting of method references.Arrester
I feel the opposite. I'm thinking about abandoning method references in favor of lambda's for the same reason I would use var over Class, it's less noisy, and if the class name changes, I might not have a breaking import. Recently I had to deal with class names changing a lot, where using a lambda wouldn't have caused a compile time error.Impi
G
19

Long lambda expressions consisting of several statements may reduce the readability of your code. In such a case, extracting those statements in a method and referencing it may be a better choice.

The other reason may be re-usability. Instead of copy&pasting your lambda expression of few statements, you can construct a method and call it from different places of your code.

Glisson answered 30/6, 2014 at 10:29 Comment(3)
Compare: houses.map(House::getName) and houses.map(h -> h.getName()). The lambda takes two less characters. It is true that the type is not explicit, but any IDE would tell you, and besides, lambdas should be used when type is obvious. I might agree with you with reusability, but lambdas are precisely tiny so they can be chained instead of creating big specific methods. In that sense, small methods are more reusable than some big and complex method, and due to lambdas's clarity (and to certain degree, verbosity) they are still easy to read.Omega
I'm with Gerald. Readability actually suffers when you move out code, so you have to jump to it to keep reading, then jump back. You want to have all the relevant code in the same place. Also, House is a very benign example; what about ThreeStoryRedBrickHouseWithBlueDoors. I prefer method references for multiple-argument lambdas, and sometimes to emphasize that the lambda is only about a single method call. There is less to go wrong with a method reference: you might misspell the argument at use site, accidentally referring to a variable from outer scope, etc.Vibratory
@Omega I disagree with your first statement. If you use well describing method names and if you extract large piles of code, moving code improves readability. If you use obfuscating method names I agree with you.Dimension
B
11

As user stuchl4n3k wrote in comments to the question, there may be exceptions occuring.

Let's consider that some variable field is null, then:

field = null;
runThisLater(()->field.method());
field = new SomeObject();

will not crash, while

field = null;
runThisLater(field::method);
field = new SomeObject();

will crash with java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Class java.lang.Object.getClass()' at a method reference statement line, at least on Android.

Today's IntelliJ notes that the refactor "may change semantics" while suggesting this refactoring.

This happens when we do "referencing" of the instance method of a particular object. Why? Lets check first two paragraphs of 15.13.3. Run-Time Evaluation of Method References:

At run time, evaluation of a method reference expression is similar to evaluation of a class instance creation expression, insofar as normal completion produces a reference to an object. Evaluation of a method reference expression is distinct from invocation of the method itself.

First, if the method reference expression begins with an ExpressionName or a Primary, this subexpression is evaluated. If the subexpression evaluates to null, a NullPointerException is raised, and the method reference expression completes abruptly. If the subexpression completes abruptly, the method reference expression completes abruptly for the same reason.

In the case of a lambda expression, I'm unsure. The final type is derived in compile-time from the method declaration. This is just simplification of what is going on exactly. But, let's assume that the method runThisLater has been declared as void runThisLater(SamType obj), where SamType is some functional interface. Then runThisLater(()->field.method()); translates into something like:

runThisLater(new SamType() {  
    void doSomething() { 
        field.method();
    }
});

Additional info:

Bearcat answered 18/1, 2021 at 10:21 Comment(3)
maybe this is exactly happening to me, while using onDraw function in treeObserver.onDrawListner, if I use lambda, app would run, but if use method reference it would crashRepresentative
The first example with runThisLater(()->field.method()); does not compile, as field is not effectively final.Feldt
The field should not be local variable, but field. In this example "field" is just simple case of part of lambda expression compiler should evaluate. Instead of accessing field It can be call of some method or even more complex expression. As for local variable it's possible for compiler (in Java) to see a result of expression evaluation (e.g. it's immediately resolved as null) as it can't be altered in different thread.Bobbery
D
2

While it is true that all methods references can be expressed as lambdas, there is a potential difference in semantics when side effects are involved. @areacode's example throwing an NPE in one case but not in the other is very explicit regarding the involved side effect. However, there is a more subtle case you could run into when working with CompletableFuture:

Let's simulate a task that takes a while (2 seconds) to complete via the following helper function slow:

private static <T> Supplier<T> slow(T s) {
    return () -> {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {}
        return s;
    };
}

Then

var result =
    CompletableFuture.supplyAsync(slow(Function.identity()))
        .thenCompose(supplyAsync(slow("foo"))::thenApply);

Effectively runs both async tasks in parallel allowing the future to complete after roughly 2 seconds.

On the other hand if we refactor the ::thenApply method reference into a lambda, both async tasks would run sequentially one after each other and the future only completes after about 4 seconds.

Side note: while the example seems contrived, it does come up when you try to regain the applicative instance hidden in the future.

Dulaney answered 20/2, 2022 at 12:4 Comment(2)
Why does this happen exactly? I've tried it on my local and I'm getting the sequential runs with lambda. This is with OpenJDK11.Detestable
@zivce, when the argument to thenCompose is evaluated it causes a call to supplyAsync(slow("foo")) to obtain the method reference on the returned instance. OTOH, when refactoring to lambda, supplyAsync(slow("foo")) is only called once the lambda is evaluated, which is after the first future completed. Because only then will the argument to the lambda be available.Dulaney
T
0

My impression is that when answering this question there are typically two topics mixed up: 1) whether to use a lambda expression or a method reference when all the operation does is call a method and 2) whether to reduce the logic inside a lambda expression by using multiple or just one method. The main argument of the accepted answer by Brian seems to fall into that very trap. Yes, it is often a case-by-case decision whether to reduce a lambda expression that holds some logic by extracting that logic into a speaking name. For this decision there are imho some guidelines one can apply, like a) the more complex the logic and b) the more reusable the logic the more this indicates it should be a method.

However, the other question whether to write a method call like this user->user.getId() or like this User::getId. And this is the one that IntelliJ will try to force towards the latter approach.

But for both decisions, there isn't a clearly better format. While in the example above the method reference seems more succinct in expressing the main meaning of the function, consider this: Stream.of(doggoA,doggoB).foreach(dog -> dog.barkLoudly()) vs Stream.of(doggoA,doggoB).foreach(BigMouthedDogBreed::barkLoudly) In the flow of a lambda expression you typically don't care about the exact type of an object you rather need enough information to derive the type and the primary piece of information what happens next in the flow, the method name is the most crucial bit of information in that expression. The class name just distracts from that in many such cases.

In a case like this, for instance,

userWallets.stream()
.map(w -> w.getPremiumCurrency())
.foreach(currency -> print(currency)

vs

userWallets.stream()
.map(UserCurrencyWallet::getPremiumCurrency)
.foreach(UserCurrencyHandler::print)

the type of the collection is likely clear when you reach the code and if not the name would make it clear enough. We typically would be trying to understand to conversion that goes on and what happens to the elements in the stream, that is much more easily readable in the first approach. The variables help us remember what type of object we operate on enough to understand but they don't distract with details we don't need. Note that this is often context depending - how well does a reader at that point understand the context and what is the particular complexity (e.g. are type details important, are the types particularly long or short and succinct anyway).

However, the important point here is that 1) there are two separate issues that often get conflated and 2) both can only be decided on a case-by-case basis, different teams can have different preferences and draw the lines differently. And finally 3) IntelliJ did pick a strict side and nudges people into one direction - which I personally find counterproductive.

P.S. As often when you try to force one way - personally I feel method references fine in a lot of cases and would often argue for extracting logic from lambdas into methods as soon as they become more than a one-liner, but the more I see method references and get used to them being used broadly due to IntelliJs guideline the more I find them cumbersome, because they are also used when a succinct lambda expression that just calls the respective method and uses short but speaking variables would be much more readable from my pov.

Tuggle answered 31/10, 2023 at 14:26 Comment(0)
I
0

In my experience, a method reference is a shortcut to create a lambda expression to the method at the time the reference is created. Consider these example with all 4 kinds of method reference, where reference() is equal to realLambda(), not betterLambda():

interface A{
    void b();
}

class C{
    A a;

    static void d(){
    }

    Runnable reference(){
        return this.a::b;
    }
    //same with reference()
    Runnable realLambda(){
        A currentA = this.a;
        return () -> currentA.b();
    }
    //different from reference()
    Runnable betterLambda(){
        return () -> this.a.b();
    }

    static Supplier<C> referenceSupplier(){
        return C::new
    }
    //same with referenceSupplier()
    static Supplier<C> lambdaSupplier(){
        return () -> new C()
    }

    static Consumer<A> referenceConsumer(){
        return A::b
    }
    //same with referenceConsumer()
    static Consumer<A> lambdaConsumer(){
        return (it) -> it.b()
    }

    static Runnable referenceStatic(){
        return C::d
    }
    //same with referenceStatic()
    static Runnable lambdaStatic(){
        return () -> C.d()
    }
}
Infeasible answered 19/4 at 8:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.