123

Do lambda expressions have any use other than saving lines of code?

Are there any special features provided by lambdas which solved problems which weren't easy to solve? The typical usage I've seen is that instead of writing this:

Comparator<Developer> byName = new Comparator<Developer>() {
  @Override
  public int compare(Developer o1, Developer o2) {
    return o1.getName().compareTo(o2.getName());
  }
};

We can use a lambda expression to shorten the code:

Comparator<Developer> byName =
(Developer o1, Developer o2) -> o1.getName().compareTo(o2.getName());
dev8080
  • 3,950
  • 1
  • 12
  • 18
Vikash
  • 2,046
  • 3
  • 23
  • 30
  • 29
    Note that it can be even shorter: `(o1, o2) -> o1.getName().compareTo(o2.getName())` – Thorbjørn Ravn Andersen Nov 21 '17 at 07:13
  • 93
    or still shorter: `Comparator.comparing(Developer::getName)` – Thomas Kläger Nov 21 '17 at 07:24
  • 33
    I wouldn't underestimate that benefit. When things are that much easier, you're more likely to do things the "right" way. There's often some tension between doing writing code that's more maintainable in the long term, vs easier to jot down in the short term. Lambdas reduce that tension, and so help you write cleaner code. – yshavit Nov 21 '17 at 07:33
  • 5
    Lambdas are closures, which makes the space savings even bigger, since now you don't have to add a parameterized constructor, fields, assignment code, etc to your replacement class. – CodesInChaos Nov 21 '17 at 10:16
  • 10
    see [Why are Python lambdas useful?](https://stackoverflow.com/q/890128/995714), [What is a good use of lambda expressions](https://stackoverflow.com/q/25376/995714), [C# Lambda expressions: Why should I use them?](https://stackoverflow.com/q/167343/995714), [Why use lambda functions?](https://stackoverflow.com/q/3259322/995714) – phuclv Nov 21 '17 at 11:32
  • 1
    Why not do the same thing for every type, not just functions? E.g. `Integer sum = new Integer() { { this.setToSum(term0, term1); } };`. That would be absurdly over-complicated, right? The value of lambda expressions is that they make delegates not be absurdly over-complicated, either. – Jonathan Cast Nov 21 '17 at 12:16
  • 2
    This question could be applied to any language structure beyond binary. Conciseness matters. – jpmc26 Nov 21 '17 at 12:23
  • Basically no, other than some optimizations that might only apply to lambdas. – user253751 Nov 22 '17 at 00:27
  • 1
    @yshavit although I agree in general, good luck at debugging a concise stream->predicate->map->collector->whatever 1-line statement vs the same 10 lines in standard procedural code. I don't think lambda are that good everytime, especially for maintenance. But it can reduce the boilerplate code, sometimes. Don't use it blindly without thinking first. – spi Nov 22 '17 at 08:39
  • @S.Piller well said. Sometimes reducing boilerplate can actually make code more readable, by letting you "get to the point" faster. – shadowtalker Nov 22 '17 at 16:35
  • You don't have to name them. Which can be very annoying. And you also don't have to remember their names. Which can be very helpful. – davidbak Nov 23 '17 at 06:26
  • Rewriting core java libraries with Lambdas actually improved their performance, so there is a compilation/execution efficiency to be realized. – pojo-guy Nov 24 '17 at 02:18
  • i find lambdas make code a bit unreadable. It's why I dont like Python. If you are not very familiar with a function and/or the object it takes. For example if I saw (o1, o2) -> o1.getName().compareTo(o2.getName()), I might be confused thinking, what type of object is o1 and o2? And what is the class and function these parameters are used for? These days IDEs have so much auto generation and autocompletion I prefer things to be a little more verbose and clear as opposed to saving a few keystrokes and adding a bigger learning curve when viewing someone else's code – Cb32019 Aug 16 '20 at 21:09

11 Answers11

126

Lambda expressions do not change the set of problems you can solve with Java in general, but definitely make solving certain problems easier, just for the same reason we’re not programming in assembly language anymore. Removing redundant tasks from the programmer’s work makes life easier and allows to do things you wouldn’t even touch otherwise, just for the amount of code you would have to produce (manually).

But lambda expressions are not just saving lines of code. Lambda expressions allow you to define functions, something for which you could use anonymous inner classes as a workaround before, that’s why you can replace anonymous inner classes in these cases, but not in general.

Most notably, lambda expressions are defined independently to the functional interface they will be converted to, so there are no inherited members they could access, further, they can not access the instance of the type implementing the functional interface. Within a lambda expression, this and super have the same meaning as in the surrounding context, see also this answer. Also, you can not create new local variables shadowing local variables of the surrounding context. For the intended task of defining a function, this removes a lot of error sources, but it also implies that for other use cases, there might be anonymous inner classes which can not be converted to a lambda expression, even if implementing a functional interface.

Further, the construct new Type() { … } guarantees to produce a new distinct instance (as new always does). Anonymous inner class instances always keep a reference to their outer instance if created in a non-static context¹. In contrast, lambda expressions only capture a reference to this when needed, i.e. if they access this or a non-static member. And they produce instances of an intentionally unspecified identity, which allows the implementation to decide at runtime whether to reuse existing instances (see also “Does a lambda expression create an object on the heap every time it's executed?”).

These differences apply to your example. Your anonymous inner class construct will always produce a new instance, also it may capture a reference to the outer instance, whereas your (Developer o1, Developer o2) -> o1.getName().compareTo(o2.getName()) is a non-capturing lambda expression that will evaluate to a singleton in typical implementations. Further, it doesn’t produce a .class file on your hard drive.

Given the differences regarding both, semantic and performance, lambda expressions may change the way programmers will solve certain problems in the future, of course, also due to the new APIs embracing ideas of functional programming utilizing the new language features. See also Java 8 lambda expression and first-class values.


¹ From JDK 1.1 to JDK 17. Starting with JDK 18, inner classes may not retain a reference to the outer instance if it is not used. For compatibility reasons, this requires the inner class not be serializable. This only applies if you (re)compile the inner class under JDK 18 or newer with target JDK 18 or newer. See also JDK-8271717

Holger
  • 285,553
  • 42
  • 434
  • 765
56

Programming languages are not for machines to execute.

They are for programmers to think in.

Languages are a conversation with a compiler to turn our thoughts into something a machine can execute. One of the chief complaints about Java from people who come to it from other languages (or leave it for other languages) used to be that it forces a certain mental model on the programmer (i.e. everything is a class).

I'm not going to weigh in on whether that's good or bad: everything is trade-offs. But Java 8 lambdas allow programmers to think in terms of functions, which is something you previously could not do in Java.

It's the same thing as a procedural programmer learning to think in terms of classes when they come to Java: you see them gradually move from classes that are glorified structs and have 'helper' classes with a bunch of static methods and move on to something that more closely resembles a rational OO design (mea culpa).

If you just think of them as a shorter way to express anonymous inner classes then you are probably not going to find them very impressive in the same way that the procedural programmer above probably didn't think classes were any great improvement.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Jared Smith
  • 19,721
  • 5
  • 45
  • 83
  • 5
    Be careful with that though, I used to think that OOP was everything, and then I got horribly confused with entity-component-system architectures (whaddaya mean you don't have a class for each kind of game object?! and each game object isn't an OOP object?!) – user253751 Nov 22 '17 at 00:34
  • 3
    In other words, thinking **in** OOP, rather than just thinking **o*f* classes as another tool,y restricted my thinking in a bad way. – user253751 Nov 22 '17 at 00:40
  • 2
    @immibis: there are lots of different ways to “think in OOP”, so besides the common mistake of confusing “thinking in OOP” with “thinking in classes”, there are lots of ways to think in a counter productive way. Unfortunately, that happens too often right from the start, as even books and tutorials teach the inferior ones, show anti patterns in examples and spread that thinking. The assumed need to “have a class for each kind of” whatever, is just one example, contradicting the concept of abstraction, likewise, there seems to be the “OOP means writing as much boilerplate as possible” myth, etc – Holger Nov 22 '17 at 07:56
  • @immibis although in that instance it wasn't helping you, that anecdote does actually support the point: the language and paradigm provide a framework for structuring your thoughts and turning them into a design. In your case you ran up against the edges of the framework, but in another context doing that might have helped you from straying into a big ball of mud and producing an ugly design. Hence, I assume, "everything is trade-offs". Keeping the latter benefit while avoiding the former issue is a key question when deciding how "big" to make a language. – Alex Celeste Nov 22 '17 at 13:46
  • @immibis that's why it's important to learn a bunch of paradigm *well*: each reaches you about the inadequacies of the others. – Jared Smith Nov 24 '17 at 12:43
40

Saving lines of code can be viewed as a new feature, if it enables you to write a substantial chunk of logic in a shorter and clearer manner, which takes less time for others to read and understand.

Without lambda expressions (and/or method references) Stream pipelines would have been much less readable.

Think, for example, how the following Stream pipeline would have looked like if you replaced each lambda expression with an anonymous class instance.

List<String> names =
    people.stream()
          .filter(p -> p.getAge() > 21)
          .map(p -> p.getName())
          .sorted((n1,n2) -> n1.compareToIgnoreCase(n2))
          .collect(Collectors.toList());

It would be:

List<String> names =
    people.stream()
          .filter(new Predicate<Person>() {
              @Override
              public boolean test(Person p) {
                  return p.getAge() > 21;
              }
          })
          .map(new Function<Person,String>() {
              @Override
              public String apply(Person p) {
                  return p.getName();
              }
          })
          .sorted(new Comparator<String>() {
              @Override
              public int compare(String n1, String n2) {
                  return n1.compareToIgnoreCase(n2);
              }
          })
          .collect(Collectors.toList());

This is much harder to write than the version with lambda expressions, and it's much more error prone. It's also harder to understand.

And this is a relatively short pipeline.

To make this readable without lambda expressions and method references, you would have had to define variables that hold the various functional interface instances being used here, which would have split the logic of the pipeline, making it harder to understand.

Eran
  • 387,369
  • 54
  • 702
  • 768
  • 19
    I think this is a key insight. It could be argued that something is practically impossible to do in a programming language if the syntactic and cognitive overhead is too big to be useful, so that other approaches will be superior. Therefore, by reducing syntactic and cognitive overhead (like lambdas do compared to anonymous classes), pipelines such as the above become possible. More tongue-in-cheek, if writing a piece of code gets you fired from your job, it could be said that it isn't possible. – Sebastian Redl Nov 21 '17 at 15:33
  • 1
    Nitpick: You don't actually need the `Comparator` for `sorted` as it compares in natural order anyway. Maybe change it to comparing length of strings or similar, to make the example even better? – tobias_k Nov 22 '17 at 09:25
  • @tobias_k no problem. I changed it to `compareToIgnoreCase` to make it behave different than `sorted()`. – Eran Nov 22 '17 at 11:11
  • 1
    `compareToIgnoreCase` still is a bad example, as you could simply use the existing `String.CASE_INSENSITIVE_ORDER` comparator. @tobias_k’s suggestion of comparing by length (or any other property not having a built-in comparator) fits better. Judging by SO Q&A code examples, lambdas caused a lot of unnecessary comparator implementations… – Holger Dec 15 '17 at 12:23
  • 1
    Am I the only one who thinks the second example is easier to understand? – Clark Battle Jan 09 '19 at 20:54
  • @ClarkBattle no, you're probably not the only one, but I'd respectfully disagree: the first is way, way, easier to follow. There's too much extraneous information in the second. It's very easy to read the first line by line: get people who are over 21, take their names, sort alpha ignore case. The **only** lines of code in the pipeline not related to business logic are the first (get the stream) and the last (convert to list). This is *doubly* true if Java is not your primary language. – Jared Smith Dec 30 '19 at 16:40
  • 1
    "It's also harder to understand" I think the opposite. – David Mar 31 '20 at 11:08
8

Internal iteration

When iterating Java Collections, most developers tend to get an element and then process it. This is, take that item out and then use it, or reinsert it, etc. With pre-8 versions of Java, you can implement an inner class and do something like:

numbers.forEach(new Consumer<Integer>() {
    public void accept(Integer value) {
        System.out.println(value);
    }
});

Now with Java 8 you can do better and less verbose with:

numbers.forEach((Integer value) -> System.out.println(value));

or better

numbers.forEach(System.out::println);

Behaviors as arguments

Guess the following case:

public int sumAllEven(List<Integer> numbers) {
    int total = 0;

    for (int number : numbers) {
        if (number % 2 == 0) {
            total += number;
        }
    } 
    return total;
}

With Java 8 Predicate interface you can do better like so:

public int sumAll(List<Integer> numbers, Predicate<Integer> p) {
    int total = 0;

    for (int number : numbers) {
        if (p.test(number)) {
            total += number;
        }
    }
    return total;
}

Calling it like:

sumAll(numbers, n -> n % 2 == 0);

Source: DZone - Why We Need Lambda Expressions in Java

Stevoisiak
  • 23,794
  • 27
  • 122
  • 225
alseether
  • 1,889
  • 2
  • 24
  • 39
  • Probably the first case is a way to save some lines of code, but it makes code easier to read – alseether Nov 21 '17 at 07:28
  • 4
    How is internal iteration a lambda feature? The simple matter of fact is technically lambdas don’t let you do anything that you couldn’t do prior to java-8. It’s just a _more concise_ way to pass code around. – Ousmane D. Nov 21 '17 at 08:09
  • @Aominè In this case `numbers.forEach((Integer value) -> System.out.println(value));` the lambda expression is made of two parts the one on the **left** of the arrow symbol (->) listing its **parameters** and the one on the **right** containing its **body**. The compiler automatically figures out that the lambda expression has the same signature of the only non implemented method of the Consumer interface. – alseether Nov 21 '17 at 08:13
  • 5
    I didn’t ask what a lambda expression is, I am just saying internal iteration has nothing to do with lambda expressions. – Ousmane D. Nov 21 '17 at 08:15
  • 1
    @Aominè I see your point, it doesn't have anything with lambdas *per se*, but as you point out, it more like a cleaner way of writting. I think in terms of efficiency is as good as pre-8 versions – alseether Nov 21 '17 at 08:17
4

There are many benefits of using lambdas instead of inner class following as below:

  • Make the code more compactly and expressive without introducing more language syntax semantics. you already gave an example in your question.

  • By using lambdas you are happy to programming with functional-style operations on streams of elements, such as map-reduce transformations on collections. see java.util.function & java.util.stream packages documentation.

  • There is no physical classes file generated for lambdas by compiler. Thus, it makes your delivered applications smaller. How Memory assigns to lambda?

  • The compiler will optimize lambda creation if the lambda doesn't access variables out of its scope, which means the lambda instance only create once by the JVM. for more details you can see @Holger's answer of the question Is method reference caching a good idea in Java 8? .

  • Lambdas can implements multi marker interfaces besides the functional interface, but the anonymous inner classes can't implements more interfaces, for example:

    //                 v--- create the lambda locally.
    Consumer<Integer> action = (Consumer<Integer> & Serializable) it -> {/*TODO*/};
    
holi-java
  • 29,655
  • 7
  • 72
  • 83
2

Lambdas are just syntactic sugar for anonymous classes.

Before lambdas, anonymous classes can be used to achieve the same thing. Every lambda expression can be converted to an anonymous class.

If you are using IntelliJ IDEA, it can do the conversion for you:

  • Put the cursor in the lambda
  • Press alt/option + enter

enter image description here

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • 4
    ... I thought @StuartMarks has already clarified the syntactic sugar-ity (sp? gr?) of lambdas? (https://stackoverflow.com/a/15241404/3475600, https://softwareengineering.stackexchange.com/a/181743) – srborlongan Nov 21 '17 at 15:35
  • It's not just syntactic sugar but certain optimizations like creating and caching implementation classes and instances at various places if applicable. Lamba expressions use the invokedynamic feature instead of anonymous classes. Please go through https://stackoverflow.com/a/23991339/1157635 – Kumar-Sandeep Jul 18 '23 at 11:18
2

To answer your question, the matter of fact is lambdas don’t let you do anything that you couldn’t do prior to java-8, rather it enables you to write more concise code. The benefits of this, is that your code will be clearer and more flexible.

Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
  • Clearly @Holger has dissipate any doubt in terms of lambda efficiency. It's not only a way to make code cleaner, but if lambda doesn't capture any value, it will be a Singleton class (reusable) – alseether Nov 21 '17 at 08:30
  • If you dig deep down, every language feature has a difference. The question at hand is at a high level hence I wrote my answer at a high level. – Ousmane D. Nov 21 '17 at 08:33
2

One thing I don't see mentioned yet is that a lambda lets you define functionality where it's used.

So if you have some simple selection function you don't need to put it in a separate place with a bunch of boilerplate, you just write a lambda that's concise and locally relevant.

Carlos
  • 5,991
  • 6
  • 43
  • 82
1

Yes many advantages are there.

  • No need to define whole class we can pass implementation of function it self as reference.
    • Internally creation of class will create .class file while if you use lambda then class creation is avoided by compiler because in lambda you are passing function implementation instead of class.
  • Code re-usability is higher then before
  • And as you said code is shorter then normal implementation.
Sunil Kanzar
  • 1,244
  • 1
  • 9
  • 21
1

Function composition and higher order functions.

Lambda functions can be used as building blocks towards building "higher order functions" or performing "function composition". Lambda functions can be seen as reusable building blocks in this sense.

Example of Higher Order Function via lambda:

Function<IntUnaryOperator, IntUnaryOperator> twice = f -> f.andThen(f);
IntUnaryOperator plusThree = i -> i + 3;
var g = twice.apply(plusThree);
System.out.println(g.applyAsInt(7))

Example Function Composition

Predicate<String> startsWithA = (text) -> text.startsWith("A");
Predicate<String> endsWithX   = (text) -> text.endsWith("x");

Predicate<String> startsWithAAndEndsWithX =
        (text) -> startsWithA.test(text) && endsWithX.test(text);

String  input  = "A hardworking person must relax";
boolean result = startsWithAAndEndsWithX.test(input);
System.out.println(result);
Alexander Petrov
  • 9,204
  • 31
  • 70
1

One benefit not yet mentioned is my favorite: lambdas make deferred execution really easy to write.

Log4j2 uses this for example, where instead of passing a value to conditionally log (a value that may have been expensive to calculate), you can now pass a lambda to calculate that expensive value. The difference being that before, that value was being calculated every time whether it got used or not, whereas now with lambdas if your log level decides not to log that statement, then the lambda never gets called, and that expensive calculation never takes place -- a performance boost!

Could that be done without lambdas? Yes, by surrounding each log statement with if() checks, or using verbose anonymous class syntax, but at the cost of horrible code noise.

Similar examples abound. Lambdas are like having your cake and eating it too: all the efficiency of gnarly multi-line optimized code squeezed down into the visual elegance of one-liners.

Edit: As requested by commenter, an example:

Old way, where expensiveCalculation() always gets called regardless of whether this log statement will actually use it:

logger.trace("expensive value was {}", expensiveCalculation());

New lambda efficient way, where expensiveCalculation() call won't happen unless trace log level is enabled:

logger.trace("expensive value was {}", () -> expensiveCalculation());
Magnus
  • 10,736
  • 5
  • 44
  • 57
  • Please flesh out your Log4j2 example with an actual example. I'd like to see more clearly what you are saying. – alife Dec 10 '21 at 17:06
  • @alife good idea, I edited to answer to include an example, hope it helps – Magnus Dec 18 '21 at 02:59
  • @Magnus: Just to clarify further, what you're implying is that the .trace method will make a decision about whether to make a log entry, without reference to expensiveCalculation. In the "lambda efficient way" expensiveCalculation will be invoked only if .trace decides to do so. In "old way", the caller performs expensiveCalculation for every .trace invocation, which is wasted when .trace decides not to log. Is that correct? – David Vancina Mar 11 '22 at 12:32
  • @DavidVancina yes that is correct. And it's not that .trace has to be particularly clever, it just knows that when a function is passed in as an arg, that function just has to be invoked to get the true arg for logging. But it only invokes the func when it's actually gonna log something. – Magnus Jun 08 '22 at 21:10